C++ 什么时候在构造函数和析构函数中调用 this-> 是安全的
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30258639/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
When is it safe to call this-> in constructor and destructor
提问by laurisvr
I've not been able to find a conclusive answer to this so far. When is it safe to call this->
from within an object. And in particular from inside the constructor and destructor.
到目前为止,我还没有找到一个决定性的答案。什么时候this->
从对象内部调用是安全的。特别是从构造函数和析构函数内部。
And also, when using public inheritance. Is it safe to use up and downcasting on the result of the this call?
而且,在使用公共继承时。对 this 调用的结果使用 up 和 downcasting 是否安全?
So for example:
例如:
class foo
{
foo():
a(),
b(this->a)//case 1
{
this-> a = 5; //case 2
}
int a;
int b;
};
class bar: public baz
{
bar():
baz(this)//case 3 - assuming baz has a valid constructor
{
}
}
And finally the most unlikely one
最后是最不可能的
foo()
{
if(static_cast<bar*>(this));//case 4
}
Which of the above cases are legal?
以上哪些情况是合法的?
Note:I'm aware a lot of the practices above are inadvisable.
注意:我知道上面的很多做法都是不可取的。
回答by Mike Seymour
Within any non-static member function, this
points to the object that the function was called on. It's safe to use as long as that's a valid object.
在任何非静态成员函数中,this
指向调用该函数的对象。只要它是一个有效的对象,就可以安全使用。
Within the body of a constructor or destructor, there is a valid object of the class currently being constructed. However, if this is the base sub-object of some derived class, then only the base sub-object is valid at that time; so it's generally not safe to down-cast and try to access members of the derived class. For the same reason, you need to be careful calling virtual functions here, since they are dispatched according to the class being created or destroyed, not the final overrider.
在构造函数或析构函数的主体中,有一个当前正在构造的类的有效对象。但是,如果这是某个派生类的基子对象,那么此时只有基子对象有效;所以向下转换并尝试访问派生类的成员通常是不安全的。出于同样的原因,在此处调用虚函数时需要小心,因为它们是根据创建或销毁的类而不是最终覆盖程序来调度的。
Within the initialiser list of a constructor, you'll need to be careful only to access members that have been initialised; that is, members declared before the one currently being initialised.
在构造函数的初始化列表中,您只需要注意访问已初始化的成员;也就是说,在当前被初始化的成员之前声明的成员。
Up-casting to a base class is always safe, since base sub-objects are always initialised first.
向上转换到基类总是安全的,因为基子对象总是首先被初始化。
For the specific examples you just added to the question:
对于您刚刚添加到问题中的具体示例:
- case 1 is fine (if fragile), since
a
has been initialised at that point. Initialisinga
with the value ofb
would be undefined, sinceb
is initialised aftera
. - case 2 is fine: all members have been initialised at that point.
- case 3 won't compile, since there's no suitable
foo
constructor. If there were, then it would depend on what that constructor did with it - whether or not it tried to access members before they were initialised. - case 4 would be well-formed if you added the missing
)
, but dangerous if you tried to use the pointer to access the object.this
does not yet point to a validbar
object (only thefoo
part has been initialised) so accessing members ofbar
could give undefined behaviour. Simply checking whether the pointer is non-null is fine, and will always givetrue
(whether or not you apply a pointless cast).
- 情况 1 很好(如果脆弱),因为那时
a
已经初始化。a
使用 的值初始化b
将是未定义的,因为b
是在 之后初始化的a
。 - 情况 2 很好:此时所有成员都已初始化。
- 情况 3 不会编译,因为没有合适的
foo
构造函数。如果有,那么它将取决于构造函数对它做了什么——它是否在成员被初始化之前尝试访问它们。 - 如果您添加了缺少的
)
,情况 4 将是格式良好的,但如果您尝试使用指针访问对象,则很危险。this
尚未指向有效bar
对象(仅foo
部分已初始化),因此访问 的成员bar
可能会产生未定义的行为。简单地检查指针是否为非空就可以了,并且总是会给出true
(无论您是否应用无意义的转换)。
回答by Goz
There is a good entry on it at the C++ super-faq:
在 C++ super-faq 中有一个很好的条目:
https://isocpp.org/wiki/faq/ctors#using-this-in-ctors
https://isocpp.org/wiki/faq/ctors#using-this-in-ctors
Some people feel you should not use the this pointer in a constructor because the object is not fully formed yet. However you can use this in the constructor (in the {body} and even in the initialization list) if you are careful.
Here is something that always works: the {body} of a constructor (or a function called from the constructor) can reliably access the data members declared in a base class and/or the data members declared in the constructor's own class. This is because all those data members are guaranteed to have been fully constructed by the time the constructor's {body} starts executing.
Here is something that never works: the {body} of a constructor (or a function called from the constructor) cannot get down to a derived class by calling a virtual member function that is overridden in the derived class. If your goal was to get to the overridden function in the derived class, you won't get what you want. Note that you won't get to the override in the derived class independent of how you call the virtual member function: explicitly using the this pointer (e.g., this->method()), implicitly using the this pointer (e.g., method()), or even calling some other function that calls the virtual member function on your this object. The bottom line is this: even if the caller is constructing an object of a derived class, during the constructor of the base class, your object is not yet of that derived class. You have been warned.
Here is something that sometimes works: if you pass any of the data members in this object to another data member's initializer, you must make sure that the other data member has already been initialized. The good news is that you can determine whether the other data member has (or has not) been initialized using some straightforward language rules that are independent of the particular compiler you're using. The bad news is that you have to know those language rules (e.g., base class sub-objects are initialized first (look up the order if you have multiple and/or virtual inheritance!), then data members defined in the class are initialized in the order in which they appear in the class declaration). If you don't know these rules, then don't pass any data member from the this object (regardless of whether or not you explicitly use the this keyword) to any other data member's initializer! And if you do know the rules, please be careful.
有些人认为你不应该在构造函数中使用 this 指针,因为对象还没有完全形成。但是,如果您小心的话,您可以在构造函数中(在 {body} 中,甚至在初始化列表中)使用它。
这里有一些总是有效的:构造函数(或从构造函数调用的函数)的 {body} 可以可靠地访问基类中声明的数据成员和/或构造函数自己的类中声明的数据成员。这是因为所有这些数据成员都保证在构造函数的 {body} 开始执行时已经完全构造。
这里有一些永远行不通的事情:构造函数(或从构造函数调用的函数)的 {body} 无法通过调用派生类中覆盖的虚拟成员函数来深入到派生类。如果您的目标是获得派生类中的重写函数,您将不会得到您想要的。请注意,无论您如何调用虚成员函数,您都不会获得派生类中的覆盖:显式使用 this 指针(例如,this->method()),隐式使用 this 指针(例如,method( )),或者甚至调用一些其他函数来调用 this 对象上的虚成员函数。底线是这样的:即使调用者正在构造派生类的对象,在基类的构造函数期间,您的对象还不是该派生类的对象。
以下是有时有效的方法:如果将此对象中的任何数据成员传递给另一个数据成员的初始值设定项,则必须确保其他数据成员已被初始化。好消息是,您可以使用一些与您使用的特定编译器无关的简单语言规则来确定其他数据成员是否已(或尚未)初始化。坏消息是你必须知道那些语言规则(例如,基类子对象首先被初始化(如果你有多重和/或虚拟继承,请查看顺序!),然后在类中定义的数据成员在它们出现在类声明中的顺序)。如果你不知道这些规则,那就不要 t 将来自 this 对象的任何数据成员(无论您是否明确使用 this 关键字)传递给任何其他数据成员的初始化程序!如果你知道规则,请小心。
回答by Pixelchemist
The this pointer is accessible in every non-static member function ...
this 指针可在每个非静态成员函数中访问...
§9.3.2/1
§9.3.2/1
In the body of a non-static (9.3) member function, the keyword
this
is a prvalue expression whose value is the address of the object for which the function is called. The type of this in a member function of a class X is X*. If the member function is declared const, the type of this is const X*, if the member function is declared volatile, the type of this is volatile X*, and if the member function is declared const volatile, the type of this is const volatile X*.
在非静态 (9.3) 成员函数的主体中,关键字
this
是一个纯右值表达式,其值是调用该函数的对象的地址。类 X 的成员函数中 this 的类型是 X*。如果成员函数声明为const,则this的类型为const X*,如果成员函数声明为volatile,则this的类型为volatile X*,如果成员函数声明为const volatile,则this的类型为const挥发性 X*。
... where constructors and destructors are member functions ...
... 其中构造函数和析构函数是成员函数 ...
§12/1
§12/1
The default constructor (12.1), copy constructor and copy assignment operator (12.8), move constructor and move assignment operator (12.8), and destructor (12.4) are special member functions.
默认构造函数 (12.1)、复制构造函数和复制赋值运算符 (12.8)、移动构造函数和移动赋值运算符 (12.8) 以及析构函数 (12.4) 是特殊的成员函数。
... which aren't static.
...不是静态的。
§12.1/4
§12.1/4
A constructor shall not be virtual (10.3) or static (9.4).
构造函数不应是虚拟的(10.3)或静态的(9.4)。
§12.4/2
§12.4/2
A destructor shall not be static.
析构函数不应是静态的。
Thus, this
is available in constructors and destructors. But there are limitations (especially with respect to the use of this
inside the initializer list).
因此,this
在构造函数和析构函数中可用。但是有一些限制(尤其是this
在初始化列表中的使用方面)。
(Note: Inside the constructor / destructor body, the initialization of all subobjects and members is completed and they are accessible; see further down below).
(注意:在构造函数/析构函数体内,所有子对象和成员的初始化都已完成并且它们是可访问的;请参见下文)。
1. Only access object being constructed (or their subobjects) through this
.
1. 只能通过 访问正在构造的对象(或其子对象)this
。
§12.1/14
§12.1/14
During the construction of a const object, if the value of the object or any of its subobjects is accessed through a glvalue that is not obtained, directly or indirectly, from the constructor's
this
pointer, the value of the object or subobject thus obtained is unspecified.
在构造 const 对象期间,如果对象或其任何子对象的值是通过不是从构造函数的
this
指针直接或间接获得的泛左值访问的,则由此获得的对象或子对象的值是未指定的。
2. Do not call virtual functions that are overriden in a derived class in the base constructor
2.不要在基构造函数中调用派生类中被覆盖的虚函数
§12.7/4
§12.7/4
Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class's non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor's or destructor's class and not one overriding it in a more derived class. If the virtual function call uses an explicit class member access (5.2.5) and the object expression refers to the complete object of x or one of that object's base class subobjects but not x or one of its base class subobjects, the behavior is undefined.
成员函数,包括虚函数 (10.3),可以在构造或销毁 (12.6.2) 期间调用。当从构造函数或析构函数直接或间接调用虚函数时,包括在类的非静态数据成员的构造或析构期间,并且该调用适用的对象是正在构造的对象(称为 x)或析构函数,调用的函数是构造函数或析构函数类中的最终覆盖程序,而不是在派生程度更高的类中覆盖它。如果虚函数调用使用显式类成员访问(5.2.5)并且对象表达式引用 x 的完整对象或该对象的基类子对象之一,但不是 x 或其基类子对象之一,则行为未定义.
3. Do not apply dynamic_cast
to cast this
into any type other than the type under construction or any base type thereof.
3. 不适dynamic_cast
用于this
除在建类型或其任何基础类型之外的任何类型的铸造。
§12.7/6
§12.7/6
dynamic_casts (5.2.7) can be used during construction or destruction (12.6.2). When a dynamic_cast is used in a constructor (including the mem-initializer or brace-or-equal-initializer for a non-static data member) or in a destructor, or used in a function called (directly or indirectly) from a constructor or destructor, if the operand of the dynamic_cast refers to the object under construction or destruction, this object is considered to be a most derived object that has the type of the constructor or destructor's class. If the operand of the dynamic_cast refers to the object under construction or destruction and the static type of the operand is not a pointer to or object of the constructor or destructor's own class or one of its bases, the dynamic_cast results in undefined behavior.
dynamic_casts (5.2.7) 可以在构造或销毁 (12.6.2) 期间使用。当 dynamic_cast 用于构造函数(包括非静态数据成员的 mem-initializer 或括号或等号初始化器)或析构函数中,或用于从构造函数调用(直接或间接)的函数中时,或析构函数,如果 dynamic_cast 的操作数引用正在构造或销毁的对象,则该对象被认为是具有构造函数或析构函数类类型的最派生对象。如果 dynamic_cast 的操作数引用正在构造或销毁的对象,并且操作数的静态类型不是指向构造函数或析构函数自己的类或其基类之一的指针或对象,则 dynamic_cast 会导致未定义的行为。
4. Conversion of this
into base type pointer is only allowed through paths that consist of constructed base types.
4. 转换this
为基类型指针只允许通过由构造基类型组成的路径。
§12.7/3
§12.7/3
To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class X to a pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior. To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.
将指向 X 类对象的指针(泛左值)显式或隐式转换为指向 X 的直接或间接基类 B 的指针(引用),X 的构造及其所有直接或间接基的构造直接或间接从 B 派生的那些应该已经开始并且这些类的销毁应该没有完成,否则转换会导致未定义的行为。要形成指向(或访问)对象 obj 的直接非静态成员的指针, obj 的构造应已开始且其销毁尚未完成,否则指针值的计算(或访问成员value) 导致未定义的行为。
Accessing sub-objects and members in the initializer list and the constructor body
访问初始化列表和构造函数体中的子对象和成员
In principle you can access constructed / initialized objects from the initializer list, if their initialization takes place before accessing them. The order of initialization is
原则上,您可以从初始化列表访问构造/初始化的对象,如果它们的初始化发生在访问它们之前。初始化的顺序是
§12.6.2/10
§12.6.2/10
In a non-delegating constructor, initialization proceeds in the following order:
First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
Finally, the compound-statement of the constructor body is executed.
在非委托构造函数中,初始化按以下顺序进行:
首先,并且仅对于最派生类 (1.8) 的构造函数,虚拟基类按照它们在基类的有向无环图的深度优先从左到右遍历中出现的顺序进行初始化,其中“左- to-right”是派生类基类说明符列表中基类的出现顺序。
然后,直接基类按照它们出现在基说明符列表中的声明顺序进行初始化(不管内存初始化器的顺序如何)。
然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化(同样与 mem 初始化程序的顺序无关)。
最后,执行构造函数体的复合语句。