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

lau*_*svr 33 c++ constructor destructor c++11

到目前为止,我还没能找到一个确凿的答案.何时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
   {


   }

}
Run Code Online (Sandbox Code Playgroud)

最后是最不可能的一个

foo()
   {
      if(static_cast<bar*>(this));//case 4
   }
Run Code Online (Sandbox Code Playgroud)

以上哪些案件合法?

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

Mik*_*our 36

在任何非静态成员函数中,this指向调用该函数的对象.只要这是一个有效的对象,它就是安全的.

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

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

向上扩展到基类始终是安全的,因为基本子对象始终首先被初始化.

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

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

  • @Yakk:我不知道超级常见问题是什么,并且无论如何也不想与其竞争.案例4中语法错误的好位置; 我只是读它来弄清楚意图,而不编译它.我已经注意到3不会编译; 但同样,问题的意图很清楚,可以回答. (4认同)

Goz*_*Goz 19

在C++ super-faq上有一个很好的条目:

https://isocpp.org/wiki/faq/ctors#using-this-in-ctors

有些人认为你不应该在构造函数中使用this指针,因为该对象尚未完全形成.但是,如果您小心,可以在构造函数中使用它(在{body}中,甚至在初始化列表中).

以下是始终有效的内容:构造函数的{body}(或从构造函数调用的函数)可以可靠地访问在基类中声明的数据成员和/或在构造函数自己的类中声明的数据成员.这是因为在构造函数的{body}开始执行时,所有这些数据成员都保证已完全构造.

这是永远不会起作用的东西:构造函数的{body}(或从构造函数调用的函数)无法通过调用在派生类中重写的虚拟成员函数来获取派生类.如果您的目标是获取派生类中的重写函数,则无法获得所需内容.请注意,您将无法在派生类中进行覆盖,而与调用虚拟成员函数的方式无关:显式使用this指针(例如this-> method()),隐式使用this指针(例如,方法( )),甚至调用一些其他函数调用此对象上的虚拟成员函数.底线是:即使调用者正在构造派生类的对象,在基类的构造函数期间,您的对象还不是该派生类.你被警告了.

以下是有时可行的方法:如果将此对象中的任何数据成员传递给另一个数据成员的初始化程序,则必须确保已初始化其他数据成员.好消息是,您可以使用一些独立于您正在使用的特定编译器的简单语言规则来确定是否已经(或尚未)初始化其他数据成员.坏消息是您必须知道这些语言规则(例如,首先初始化基类子对象(如果您有多个和/或虚拟继承,请查找顺序!),然后在类中定义的数据成员初始化它们出现在类声明中的顺序).如果您不了解这些规则,则不要将此对象中的任何数据成员(无论您是否明确使用this关键字)传递给任何其他数据成员的初始化程序!如果你确实知道规则,请小心.


Pix*_*ist 7

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

§9.3.2/1

在非静态(9.3)成员函数的主体中,关键字this是一个prvalue表达式,其值是调用该函数的对象的地址.类X的成员函数中的类型是X*.如果成员函数声明为const,则其类型为const X*,如果成员函数声明为volatile,则其类型为volatile X*,如果成员函数声明为const volatile,则此类型为const挥发性X*.

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

§12/1

默认构造函数(12.1),复制构造函数和复制赋值运算符(12.8),移动构造函数和移动赋值运算符(12.8)以及析构函数(12.4)是特殊成员函数.

......这不是静态的.

§12.1/4

构造函数不应是虚拟(10.3)或静态(9.4).

§12.4/2

析构函数不应该是静态的.

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

(注意:在构造函数/析构函数体内,所有子对象和成员的初始化都已完成,并且可以访问它们;请参阅下面的进一步说明).

1.仅访问正在构建的对象(或其子对象)this.

§12.1/14

在构造const对象期间,如果通过未从构造函数this指针直接或间接获得的glvalue访问对象或其任何子对象的值,则未指定由此获得的对象或子对象的值.

2.不要在基础构造函数中调用在派生类中重写的虚函数

§12.7/4

成员函数,包括虚函数(10.3),可以在构造或销毁期间调用(12.6.2).当从构造函数或析构函数直接或间接调用虚函数时,包括在构造或销毁类的非静态数据成员期间,以及调用所适用的对象是正在构造的对象(称为x)或者破坏,被调用的函数是构造函数或析构函数类中的最终覆盖,而不是覆盖更多派生类的函数.如果虚函数调用使用显式类成员访问(5.2.5)并且对象表达式引用x的完整对象或该对象的基类子对象之一但不是x或其基类子对象之一,则行为未定义.

3.不得将dynamic_cast铸造this成任何类型的铸造以外的其他类型或其任何基础类型.

§12.7/6

dynamic_casts(5.2.7)可以在施工或销毁期间使用(12.6.2).在构造函数(包括用于非静态数据成员的mem-initializer或brace-or-equal-initializer)或析构函数中使用dynamic_cast时,或者在构造函数中(直接或间接)调用的函数中使用dynamic_cast时析构函数,如果dynamic_cast的操作数引用正在构造或销毁的对象,则此对象被视为具有构造函数或析构函数类的类型的派生程度最高的对象.如果dynamic_cast的操作数引用正在构造或销毁的对象,并且操作数的静态类型不是构造函数或析构函数自己的类或其基础之一的指针或对象,则dynamic_cast会导致未定义的行为.

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

§12.7/3

显式或隐式地将引用类X的对象的指针(glvalue)转换为指向X的直接或间接基类B的指针(引用),构造X及其所有直接或间接基础的构造直接或间接从B派生的应该已经开始并且这些类的销毁不应该完成,否则转换会导致不确定的行为.要形成指向对象obj的直接非静态成员(或访问其值)的指针,obj的构造应该已经开始并且它的销毁不应该完成,否则计算指针值(或访问成员) value)导致未定义的行为.

访问初始值设定项列表和构造函数体中的子对象和成员

原则上,如果在访问它们之前进行初始化,则可以从初始化列表中访问构造/初始化对象.初始化的顺序是

§12.6.2/10

在非委托构造函数中,初始化按以下顺序进行:

  • 首先,仅对于派生程度最高的类(1.8)的构造函数,虚拟基类按照它们出现在基类的有向无环图的深度优先从左到右遍历的顺序进行初始化,其中"左 - to-right"是派生类base-specifier-list中基类出现的顺序.

  • 然后,直接基类按声明顺序初始化,因为它们出现在base-specifier-list中(无论mem-initializers的顺序如何).

  • 然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化(同样不管mem-initializers的顺序如何).

  • 最后,执行构造函数体的复合语句.