在基本构造函数完成之前传递“this”:UB 或只是危险
Passing `this` before base constructors are done: UB or just dangerous?
考虑这个最小的例子(我能想到(:
struct Bar;
struct Foo {
Bar* const b;
Foo(Bar* b) : b(b) {}
};
struct Bar {
Foo* const f;
Bar(Foo* f) : f(f) {}
};
struct Baz : Bar {
Baz() : Bar(new Foo(this)) {}
};
当this
传递给 Foo
的 ctor 时,Baz
层次结构中没有创建任何内容,但Foo
和Bar
都不会对它们收到的指针做任何问题。
现在的问题是,以这种方式赠送this
是危险的,还是不确定的行为?
问题2:如果Foo::Foo(Bar*)
是具有相同语义的Foo::Foo(Bar&)
呢?我必须传递*this
,但在这种情况下,deref 运算符不会做任何事情。
这不是 UB。该对象可能尚未正确初始化(因此可能无法立即使用它(,但存储指针以供以后使用是可以的。
我必须传递*this,但在这种情况下,deref 运算符不会做任何事情。
当然会,它会取消引用指针。请记住,初始化与分配不同 - 当构造函数运行时,对象已经正确分配(否则您将无法初始化它( - 即它存在,但它处于不确定状态,直到其构造函数完成。
这个问题在标准 3.8/5 中直接回答C++:
在对象的生存期开始之前,但在分配了对象将占用的存储之后,或者在对象的生存期结束之后,在重新使用或释放对象占用的存储之前,可以使用任何指向对象将要或曾经所在的存储位置的指针,但只能以有限的方式使用。对于正在建造或销毁的物体,请参见 12.7。否则,此类指针引用分配的存储 (3.7.4.2(,并且像指针类型为 void* 一样使用该指针是明确定义的。可以取消引用此类指针,但生成的左值只能以有限的方式使用,如下所述。在以下情况下,程序具有未定义的行为:
- 该对象将是或曾经是具有非平凡析构函数的类类型,指针用作删除表达式的操作数,
- 指针用于访问非静态数据成员或调用对象的非静态成员函数,或
- 指针隐式转换为 (4.10( 到指向基类类型的指针,或者
- 指针用作static_cast的操作数 (5.2.9((除非转换为 void*,或转换为 void*,然后转换为 char*,或无符号字符*(,或
- 指针用作dynamic_cast的操作数 (5.2.7(。
此外,在 12.7/3 中:
为了显式或隐式地将引用 X 类对象的指针(glvalue(转换为指向 X 的直接或间接基类 B 的指针(引用(,X 的构造及其直接或间接派生自 B 的所有直接或间接基的构造应该已经开始,并且这些类的销毁尚未完成, 否则,转换会导致未定义的行为。
行为不是未定义的,也不一定是危险的。
Foo
和Bar
都不会对他们收到的指针做任何问题。
这是关键:您只需要知道指针指向的对象尚未完全构造。
就如果
Foo::Foo(Bar*)
是具有相同语义的Foo::Foo(Bar&)
呢?
危险性或定义性而言,两者之间实际上没有区别。
这是个好问题。 如果我们阅读 §3.8,则对象的生命周期非平凡构造函数仅在构造函数完成后启动("初始化完成"(。 几段之后,该标准界定了我们可以和不能用指针做什么"在对象的生存期开始之前,但在对象将占用的存储已分配"(并且 初始化列表中的this
指针似乎肯定适合鉴于上述定义,归入该类别(:特别是
在以下情况下,程序具有未定义的行为:
[...]
- 指针隐式转换为指向基类类型的指针,或者
[...]
在您的示例中,基数参数中的指针类型类具有基类类型,因此派生类的this
指针必须隐式转换为它。 这是未定义的行为根据上述。但......为了调用构造函数基类,编译器必须将地址隐式转换为类型指向基类的指针。 所以一定有一些例外。
在实践中,我从来不知道编译器在这种情况下会失败,除了涉及虚拟继承的案件;我肯定有遇到具有以下模式的错误:
class L;
class VB {};
class R : virtual VB { public: R( L* ); }
class L { L( char const* p ); };
class D : private virtual L, private virtual R { D(); }
D::D( char const* p ) : L( p ), R( this ) {}
为什么编译器在这里有问题,我不知道。 它能够正确转换指针,将其作为this
指针传递给L
的构造函数,但在将其传递给时没有正确执行 R
.
在这种情况下,解决方法是为 L 提供一个包装类,其中返回指针的成员函数,例如:
class LW : public L
{
public:
LW( char const* p ) : L( p ) {}
L* getAddress() { return this; }
};
D::D( char const* p ) : L( p ), R( this->getAddress(); ) {}
这一切的结果是我无法给你一个明确的答案,因为我不确定该标准的作者的意图。 在另一方面,我实际上已经看到它不起作用的情况(而不是那个很久以前(。
- 表示"accepting anything for this template argument" C++概念的通配符
- 为什么使用 "this" 指针调用派生成员函数?
- C++错误:"error: int aaa::bbb is protected within this context"
- 我可以将调用类的"this"传递给 lambda 函数吗?
- 创建具有 new in 函数和"this is nullptr"异常的对象
- 关于C++中具有多重继承"this"指针的说明
- 在noexcept 规范中是否允许使用"this"?
- 如何修复"error: ‘_1’ was not declared in this scope"?
- 'string.assign(string.data(), 5)' 是明确定义的还是 UB?
- C++调用具有 *this 属性的单个帮助程序函数
- Doees the 'this' 指针参与虚函数的多态行为
- 在 c++ 中正确定义"this"关键字?
- 使用 CTRP 时,是否访问访问父构造函数 UB 中的子属性?
- 在什么情况下,两个堆栈分配的结构对象的 this 点指向同一个地址?
- "Unable to start debugging. No process is associated with this object." - 在Visual Studio Code中使用GDB
- 为什么我会收到此错误?无法将 {lb, ub} 从<大括号括起来的初始值设定项列表>转换为 float(**)(float*, int)
- QObject::连接无法将信号连接到*this*对象的插槽
- 析构函数中的"delete this"
- 为什么成员函数内的"this"指针为空?
- 在基本构造函数完成之前传递“this”:UB 或只是危险