层次结构中子类对象的部分构造和销毁
Partial construction & destruction of an object of a child class in hierarchy
EDIT1:编辑问题以修复Yakk答案中指出的UB(这是关于原始问题的有效答案)。
请考虑以下代码:
class C
{
protected:
C(bool) : c(0) { s = new char[10]; /* init C members... */ }
void cleanup() { delete[s]; /* cleanup C members... */ } //EDIT1
C() { /* do nothing, keep C members unchanged */ }
// EDIT1: removed dtor: ~C() { /* do nothing, keep C members unchanged */ }
// EDIT1: implicitly defined default (trivial) dtor
int c;
char* s;
};
class Child1 : public C
{
public:
Child1(bool) : C(true) { }
void cleanup() { C::cleanup(); } //EDIT1
Child1() { c ++; }
// EDIT1: removed dtor: ~Child1() { }
// EDIT1: implicitly defined default (trivial) dtor
};
class Child2 : public C
{
public:
Child2() { c --; }
void cleanup() { C::cleanup(); } //EDIT1
// EDIT1: removed dtor: ~Child2() { }
// EDIT1: implicitly defined default (trivial) dtor
};
int main()
{
char storage[sizeof(Child1)]; // (0) storage for any C child instance
C* child = new(&storage) Child1(true); // (1) create in-place Child1 instance and initialize C members
//EDIT1: removed: static_cast<Child1*>(child)->~Child1(); // (2) destroy Child1 instance, keeping C members unchanged
child = new(&storage) Child2; // (3) create in-place Child2 instance, keeping C members unchanged, overwritting Child1 members
//EDIT1: removed: static_cast<Child2*>(child)->~Child2(); // (4) destroy Child2 instance, keeping C members unchanged
child = new(&storage) Child1(true); // (5) create in-place Child1 instance, keeping C members unchanged, overwritting Child2 members
//EDIT1: removed: static_cast<Child1*>(child)->~Child1(); // (6) destroy Child1 instance, keeping C members unchanged
child->cleanup(); // (7) cleanup Child1 & C members [EDIT1]
return 0;
}
- 在第 (1) 行,
Child1
实例是使用非默认 ctorChild1(bool)
"就地"创建的。这导致通过非默认 ctorC(bool)
初始化父类C
成员。 - 在第 (3) 行,
Child2
实例是使用默认 ctorChild2
"就地"创建的。这将覆盖 Child1 实例[EDIT1] 并调用父类C
的默认 ctor,该默认为空以保持C
成员不变。
Child1
实例被销毁。这将调用父类的 dtorC
,它自愿实现为空,以保持C
成员不变。在此步骤中,Child2
实例已经能够访问父类C
受保护的成员,尽管Child1
实例在第 (3) 行执行的覆盖操作中
上面描述的模式允许我实现我的主要目标:创建和
C
的任何子级的[EDIT1]实例,保持C
成员不变。此外,使用非默认 ctor,我有一种方法可以初始化C
成员(例如在第 (1) 行)。
但是,此模式有几个缺点:
- 类
C
成员不能是常量或引用。[编辑1] - 类
C
、类C
的父级和类C
成员必须有一个明确定义的默认 ctor 实现为空(即类似琐碎的)[EDIT1] - 类
C
必须有一个微不足道的 dtor。[编辑1]
C
成员不能是 const 或引用,并且必须具有简单的默认 ctor 和 dtor。(相同的规则适用于任何子成员。C(bool)
相同,但遗憾的是C++不支持。我的问题:
- 上面描述的模式是定义的行为吗?[编辑1]
- 有没有其他方法可以实现相同的目标([
C
的[EDIT1]子类实例,保持父类C
成员不变)而没有上面列出的缺点C
ctor 和 dtor,这将是完美的。注1:在实际应用中,C
类可能相当大,C
子项的构造
注2[EDIT1]:类C
和子类中的普通 dtor 需要防止在析构函数的错误调用时出现未定义行为;根据 C++ 标准的 §3.8/1,具有普通 dtor 的对象的生存期不会在调用析构函数时结束。
你正在做的是未定义的行为。
要获得格式正确的程序,只能按其正确的类型销毁对象。 销毁后,只能将其存储作为未初始化的缓冲区进行访问。 重新创建时,不能保证变量的状态,也不能保证它们共享以前的状态。
如果需要此类行为,可以实现手动继承方案,例如 C 程序员在需要 OO 层次结构时使用的方案。
这允许独立于数据的 OO-标识存储状态数据,并允许您动态更改对象的 OO-标识。
下面是一个玩具示例:
struct Base_vtable {
void(*display)(Base const*);
};
struct Base {
static init_vtable(Base_vtable* vtable) {
vtable->display = display_raw;
}
static Base_vtable make_vtable() {
Base_vtable vtable;
init_vtable(&vtable);
return vtable;
}
static Base_vtable const* get_vtable() {
static const auto vtable = make_vtable();
return &vtable;
}
Base_vtable const* vtable_data = nullptr;
Base_vtable const* vtable() const { return vtable_data; }
std::array<char, 1000*1000> big_buffer;
std::string name;
static void display_raw(Base const* self) {
std::cout << self->name;
}
void display() {
vtable()->display(this);
}
static void ctor(Base* self) {
self->vtable_data = get_vtable();
}
static void dtor(Base* self) {
}
};
struct Derived_vtable:Base_vtable {
int(*sum)(Derived const*);
};
struct Derived:Base {
Derived_vtable const* vtable() {
return static_cast<Derived_vtable const*>(vtable_data);
}
static void init_vtable(Derived_vtable* vtable) {
vtable->print = display_raw;
vtable->sum = sum_raw;
}
static Derived_vtable make_vtable() {
Derived_vtable d;
init_vtable(&d);
return d;
}
static Derived_vtable const* get_vtable() {
static const Derived_vtable vtable = make_vtable();
return &vtable;
}
static int sum_raw(Derived const* self) {
int r = 0;
for (auto&& c:big_buffer)
r+=c;
return r;
}
static void display_raw(Derived const* self) {
std::cout << "Derived: ";
Base::display_raw(self);
}
int sum() const {
return vtable()->sum(this);
}
static void ctor(Derived* self) {
Base::ctor(self);
self->vtable_data = get_vtable();
}
static void dtor(Derived* self) {
Base::dtor(self);
}
};
这与C++想要使用默认C++OO系统时为您执行的操作非常相似。 除了现在我们有细粒度的控制,我们可以改变我们的各种 ctor 做什么。
我可以将我的状态与我的虚拟类型分离,允许Derived
拥有多个不同的 vtables,并在我想要时更改其行为。 这些状态之间的过渡可以做任何我想做的事情。
您的解决方案的问题在于,允许编译器将销毁对象的状态用于它选择的任何目的 - 它可以将其用作交换寄存器的空间。 它可以假定存储在被破坏结构中的对象是悬空指针,证明指针为 null 或指向该存储,确定如果不是 null,我们将取消引用它,如果取消引用则行为为 UB,然后正确优化代码以了解指针必须为 null 并且不检查它, 消除死代码分支。
一旦你深入研究了未定义的行为,你就不得不在将来的编译器的每一次迭代中维护你的代码,这可能会通过在标准下做完全合法的事情来破坏你的代码。 这是一个非常繁重的负载,除非您正在编写一次性代码。
在详尽地阅读了标准之后,我可以回答我自己问题的第一部分。
正如 Yakk 所提到的,我提出的第一个方案(原始问题)是 UB,因为调用对象的析构函数会结束其生命周期,除非析构函数是微不足道的。 标准的§3.8/1规定:
类型
T
对象的生存期在以下情况下结束:— 如果
T
是具有非平凡析构函数 (12.4) 的类类型,则析构函数调用开始,或者— 对象占用的存储被重复使用或释放。
在我提出的更新方案 (EDIT1) 中,根本不调用对象的析构函数,而是用新对象覆盖对象。我虽然这会摆脱 UB,但是标准的相同 §3.8/1 明确指出,当调用对象的析构函数或重用它占用的存储时,对象的生存期结束,这正是覆盖所做的。(具体而言,这使用了子指针 UB。
然后,我提出的更新方案与第一个方案一样是UB。
关于我问题的第二部分,Yakk提供了一个有效的解决方案。
- 从父类方法返回子类对象
- 对静态分配的子类对象进行静态分配的纯虚拟父类引用是否合法?
- 我们可以在没有新实例化的情况下声明一个抽象方法来返回抽象超类中的子类对象吗
- 如何在 BaseClass 指针数组中存储指向子类对象的指针?
- 子类对象列表重新解释为基类对象列表?(C++11).
- 如何将字符串(ID)映射到新的子类对象C 的创建
- C++:使用父类运算符函数更新子类对象的继承变量
- 指向子类对象的指针的静态数组出现问题
- cpp 从需要超类对象的函数访问子类对象方法
- 制作可以存储子类对象的超类类型的向量
- 我构造子类对象的方式有什么问题?
- 识别基类指向的子类对象
- C++维护子类对象的混合集合
- 在构造子类对象的过程中,更改一个类属性的数据类型会导致segfault
- 将子类对象传递给采用超类对象的函数
- 从基类模板化列表中实例化子类对象
- 创建子类对象时, 是为超类创建的第二个对象
- 如何从私有成员子类对象捕获异常
- 如何让一组不同的子类对象从另一组共享基类的一个静态变量
- visual超级类有可能拥有子类对象吗?c++