什么时候在构造函数和析构函数中调用 this-> 是安全的

When is it safe to call this-> in constructor and destructor

到目前为止,我还没有找到这个问题的最终答案。什么时候从对象内部调用 this-> 是安全的。特别是从构造函数和析构函数内部。

而且,当使用 public 继承时。对 this 调用的结果使用向上和向下转换是否安全?

例如:

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
   {


   }

}

最后是最不可能的

foo()
   {
      if(static_cast<bar*>(this));//case 4
   }

以上哪些情况是合法的?

注意:我知道上面的很多做法都是不可取的。

在 C++ 超级常见问题解答中有一个很好的条目:

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 指向函数被调用的对象。只要是有效对象就可以安全使用。

在构造函数或析构函数的主体中,有一个当前正在构造的 class 的有效对象。但是,如果这是某个派生的基础子对象class,那么此时只有基础子对象有效;所以向下转换并尝试访问派生 class 的成员通常是不安全的。出于同样的原因,你需要小心在这里调用虚函数,因为它们是根据正在创建或销毁的 class 而不是最终的覆盖器来调度的。

在构造函数的初始化列表中,您只需要注意访问已初始化的成员;也就是说,在当前正在初始化的成员之前声明的成员。

向上转换为基础 class 总是安全的,因为基础子对象总是首先初始化。

对于您刚刚添加到问题中的具体示例:

  • 案例 1 很好(如果脆弱的话),因为 a 已经在那个时候初始化了。使用 b 的值初始化 a 将是未定义的,因为 ba.
  • 之后初始化
  • 情况 2 很好:此时所有成员都已初始化。
  • 案例 3 无法编译,因为没有合适的 foo 构造函数。如果有,那么这将取决于构造函数对它做了什么——它是否在成员初始化之前尝试访问它们。
  • 如果您添加缺失的 ),情况 4 将是合式的,但如果您尝试使用指针访问该对象,则很危险。 this 尚未指向有效的 bar 对象(仅 foo 部分已初始化)因此访问 bar 的成员可能会产生未定义的行为。简单地检查指针是否为非空就可以了,并且总是会给出 true (无论您是否应用无意义的转换)。

this 指针在每个非静态成员函数中都可以访问...

§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*.

...其中构造函数和析构函数是成员函数...

§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/4

A constructor shall not be virtual (10.3) or static (9.4).

§12.4/2

A destructor shall not be static.

因此,this 在构造函数和析构函数中可用。但是也有限制(特别是在初始化列表中使用 this)。

(注意:在构造函数/析构函数体内,所有子对象和成员的初始化已经完成,并且可以访问;见下文)。

1.仅通过 this.

访问正在构造的对象(或其子对象)

§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.

2。不要在基本构造函数

中调用在派生class中重写的虚函数

§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.

3。不要应用 dynamic_castthis 转换为正在构造的类型或其任何基类型以外的任何类型。

§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.

4.仅允许通过由构造的基类型组成的路径将 this 转换为基类型指针。

§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.

访问初始化列表和构造函数体中的子对象和成员

原则上您可以从初始化列表中访问构造/初始化对象,如果它们的初始化发生在访问它们之前。 初始化顺序为

§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.