通过放置 new 来延迟真正的基类构建
我问以下方法是否(以及为什么)a)合法且b)道德。我询问的重点是 C++03,但也欢迎有关 C++11 的注释。这个想法是为了防止本身可以默认构造的派生类实现愚蠢的 B::B(int foo) : A(foo) {}
构造函数。
class Base {
private:
int i;
Base(int i) : i(i) {}
protected:
Base() {}
public:
static Base* create(int i);
};
class Derived : public Base {
};
Base* Base::create(int i) {
Derived* d = new Derived();
Base* b = static_cast<Base*>(d);
delete b;
new(b) Base(i);
return d;
}
我的直觉告诉我,这里有些可疑。如果任何 Derived
类在其构造函数中访问 Base
成员,我想在其他地方,但否则我很难找到该方法不好的有效原因。
无论如何,如果您认为这是一种可以接受的方法,那么如何处理引用成员(例如 int& Base::j
)?
注意:这是 如何在 C++03 中伪造构造函数继承?。
编辑:我在发布问题时一定是分心了。当然,我的意思不是delete b
,而是b->~Base()
。怪我低血糖!
I'm asking if (and why) the following approach is a) legal and b) moral. I'm asking with emphasis on C++03, but notes on C++11 are welcome, too. The idea is to prevent derived classes that could themselves be default constructible from implementing stupid B::B(int foo) : A(foo) {}
constructors.
class Base {
private:
int i;
Base(int i) : i(i) {}
protected:
Base() {}
public:
static Base* create(int i);
};
class Derived : public Base {
};
Base* Base::create(int i) {
Derived* d = new Derived();
Base* b = static_cast<Base*>(d);
delete b;
new(b) Base(i);
return d;
}
My gut is telling me, that something is fishy here. If any Derived
class accesses Base
members in its constructor, I want to be somewhere else, but otherwise I have trouble seeing valid reasons why the approach is bad.
Anyway, if you think this is an acceptable approach, how would to deal with reference members (something like int& Base::j
)?
Note: This is a follow-up question to How can I fake constructor inheritance in C++03?.
Edit: I must have been distracted when posting the question. Of course, instead of delete b
I meant b->~Base()
. I blame low blood sugar!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
该代码不正确并触发未定义的行为。您的
Base
类没有虚拟析构函数,这意味着delete b
将导致未定义的行为。UB 调用
delete
的原因包括它不会释放派生资源(这似乎就是代码的目的,哎呀!),事实上,它将尝试释放分配的内存,这可能有效也可能无效,具体取决于两个对象的布局。如果未能释放内存,它可能会崩溃,如果成功,下一行中的放置 new 调用将尝试初始化内存中已释放的对象...即使您更改了代码(试图避免释放)问题)到:
如果没有
删除
,因此特定的未定义行为源消失了,代码仍然是未定义行为(可能有太多方式甚至无法提及) 。一旦对析构函数的调用仍然是 UB,即使事实并非如此,您重新创建了Base
类型这一事实意味着该对象的动态分派可能会将该对象视为Base
而不是Derived
对象(在vtable
的情况下,指向运行时类型信息的vptr
将引用到Base
,而不是派生
)并且可能还有两三个其他的事情可能会出错,我现在无法想到......
The code is incorrect and triggers undefined behavior. Your
Base
class does not have a virtual destructor, which means that thedelete b
will cause Undefined Behavior.The reasons for UB in the call to
delete
range from the fact that it will not release the derived resources (which seems to be the purpose of the code, ouch!), to the fact that it will try to release the allocated memory, which might work or not, depending on the layout of both objects. If it fails to deallocate the memory it will probably crash, if it succeeds the placement new call in the following line will try to initialize an object in memory that has already been released...Even if you changed the code (trying to avoid deallocation problems) to:
Where there is no
delete
and thus that particular source of Undefined Behavior is gone, the code is still undefined behavior (probably in too many ways to even mention). For once the call to the destructor is still UB, even if that was not, the fact that you recreated theBase
type means that dynamic dispatch for that object will probably consider the object to be aBase
rather than aDerived
object (in the case ofvtable
s, thevptr
that points to the RunTime Type information will refer toBase
, rather thanDerived
)And there are probably two or three other things that can go wrong and I cannot think of right now...
delete b
不仅仅调用Base
的析构函数,它还释放new
返回的内存并调用Derived
s析构函数[假设您希望Base
有一个虚拟析构函数...如果您希望它是非虚拟的,则该行为一开始就未定义]。这意味着您随后使用placement new 会在内存中构造一个不再有效的全新Base
对象,并且您永远不会替换之前销毁的Derived
部分。简而言之,你所做的一切都没有接近正确的行为。坦率地说,我不明白你想做什么......为什么必须默认构造
Derived
,而不是仅仅转发参数?这并不愚蠢,而是做事的方式。delete b
does not just callBase
s destructor, it also deallocates the memory returned bynew
and callsDerived
s destructor [assuming you intendedBase
to have a virtual destructor... if you intended it to be non-virtual, the behavior is simply undefined to begin with]. Which means your subsequent use of placement new is constructing a whole newBase
object in memory that is no longer valid, and you are never replacing the portion ofDerived
that you destroyed earlier. In short, nothing you have done even approaches correct behavior.Frankly, I don't see what you're trying to do... why must
Derived
be default constructed, instead of just forwarding an argument? Its not stupid, its the way things are done.