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

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

本文关键字:gt 安全 this- 调用 构造函数 析构函数 什么时候      更新时间:2023-10-16

到目前为止,我还没有找到一个决定性的答案。什么时候从对象内调用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
}

以上哪种情况是合法的?

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

在任何非静态成员函数中,this都指向调用该函数的对象。只要是有效对象,就可以安全使用。

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

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

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

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

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

在C++超级faq:中有一个很好的条目

https://isocpp.org/wiki/faq/ctors#using-这是

有些人认为您不应该在构造函数中使用this指针因为物体还没有完全成形。但是您可以使用在构造函数中(在{body}中,甚至在初始化列表中)如果你小心的话。

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

这里有一些永远不起作用的东西:构造函数(或从构造函数调用的函数)不能归结为派生的类中重写的虚拟成员函数派生类。如果您的目标是访问中的重写函数派生类,你不会得到你想要的。注意,你不会获取派生类中的重写,与调用方式无关虚拟成员函数:显式地使用this指针(例如。,this->method()),隐式使用this指针(例如method(,甚至调用调用虚拟成员的其他函数函数。底线是:即使在基类的构造函数,您的对象还不是派生的班您已收到警告。

以下是有时有效的方法:如果您传递任何数据该对象中的成员到另一个数据成员的初始值设定项,必须确保其他数据成员已经初始化。这个好消息是,您可以确定其他数据成员是否具有(或尚未)使用某种简单的语言进行初始化独立于您使用的特定编译器的规则。坏消息是,你必须知道那些语言规则(例如,基本类子对象首先初始化(如果有多重和/或虚拟继承!),然后在中定义的数据成员类按照它们在类声明)。如果你不知道这些规则,就不要通过任何规则该对象的数据成员(无论您是否显式使用this关键字)添加到任何其他数据成员的初始化器!如果你知道规则,请小心。

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

§9.3.2/1

在非静态(9.3)成员函数的主体中,关键字this是一个prvalue表达式,其值是为其调用函数的对象的地址。在类X的成员函数中,它的类型是X*。如果成员函数被声明为const,则this的类型为const X*,如果成员函数声明为volatile,则this类型为volatileX*,并且如果成员函数宣布为const volatile则this类型是const volabileX*。

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

§12/1

默认构造函数(12.1)、复制构造函数和复制赋值运算符(12.8)、移动构造函数和移动赋值运算符(1280)以及析构函数(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_castthis强制转换为除正在构建的类型或其任何基类型之外的任何类型

§12.7/6

dynamic_casts(5.2.7)可在构造或销毁(12.6.2)期间使用。当dynamic_cast用于构造函数(包括非静态数据成员的mem初始值设定项或brace或equal初始值设定值)或析构函数中,或用于从构造函数或析构构函数(直接或间接)调用的函数中时,如果dynamic_cast的操作数引用了正在构造或销毁的对象,则该对象被认为是具有构造函数或销毁函数类类型的派生程度最高的对象。如果dynamic_cast的操作数引用了正在构造或销毁的对象,并且操作数的静态类型不是指向构造函数或销毁函数自身类或其基之一的指针或对象,则dynamic_cast会导致未定义的行为。

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

§12.7/3

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

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

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

§12.6.2/10

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

  • 首先,仅对于最派生类(1.8)的构造函数,虚拟基类按照它们在基类的有向无环图的深度优先从左到右遍历中出现的顺序进行初始化,其中"从左到左"是派生类基类说明符列表中基类的出现顺序。

  • 然后,直接基类按照它们出现在基说明符列表中的声明顺序进行初始化(无论mem初始化器的顺序如何)。

  • 然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化(同样,与mem初始化器的顺序无关)。

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