将一个 C + + 对象传递给它自己的构造函数合法吗?

我很惊讶地意外地发现下面的作品:

#include <iostream>
int main(int argc, char** argv)
{
struct Foo {
Foo(Foo& bar) {
std::cout << &bar << std::endl;
}
};
Foo foo(foo); // I can't believe this works...
std::cout << &foo << std::endl; // but it does...
}

我将构造对象的地址传递给它自己的构造函数。这看起来像是源代码级别的循环定义。这些标准真的允许你在构造对象之前就把对象传递给函数吗? 或者这是一个未定义行为?

I suppose it's not that odd given that all class member functions already have a pointer to the data for their class instance as an implicit parameter. And the layout of the data members is fixed at compile time.

注意,我并不是在问这是否有用或者是个好主意; 我只是在到处修修补补,以便更多地了解课程。

8646 次浏览

这不是未定义行为。虽然 foo是未初始化的,但您使用它的方式是标准所允许的。在为对象分配空间之后,但在对象完全初始化之前,允许以有限的方式使用它。允许绑定到该变量的引用并获取其地址。

缺陷报告363: 从自我初始化类涵盖了这方面的内容,它表示:

如果是这样,UDT 的自初始化的语义是什么? 比如说

 #include <stdio.h>


struct A {
A()           { printf("A::A() %p\n",            this);     }
A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); }
~A()          { printf("A::~A() %p\n",           this);     }
};


int main()
{
A a=a;
}

可以汇编和打印:

A::A(const A&) 0253FDD8 0253FDD8
A::~A() 0253FDD8

决议是:

3.8[ basic.life ]第6段表明这里的引用是有效的。允许在完全初始化类对象之前获取其地址,并且允许将其作为参数传递给引用参数,只要引用可以直接绑定。除了未能将 printfs 中的% p 的指针强制转换为 void * 之外,这些示例都符合标准。

3.8 [basic.life]节对 C + + 14标准草案的完整引述如下:

类似地,在对象的生存期开始之前,但在 对象将占用的存储空间已经分配,或者,在 对象的生存期已结束,并且在 对象被重用或释放,任何引用 原来的物体可以被使用,但只能在有限的方式。对于一个物体 under construction or destruction, see 12.7. Otherwise, such a glvalue 指已分配的存储器(3.7.4.2) ,并使用 glvalue that do not depend on its value is well-defined. The program 未定义行为:

  • 将左值到右值的转换(4.1)应用于这样的 glvalue,

  • 值用于访问非静态数据成员或调用 object, or

  • the glvalue is bound to a reference to a virtual base class (8.5.3), or

  • the glvalue is used as the operand of a dynamic_cast (5.2.7) or as the operand of typeid.

我们不会对 foo做任何按照上面的子弹定义的未定义行为。

If we try this with Clang, we see an ominous warning (看现场直播):

警告: 变量‘ foo’在它自己的初始化中使用时是未初始化的[-Wuninitialization ]

这是自 从未初始化的自动变量产生一个不确定的值是未定义行为的以来的一个有效警告。但是,在这种情况下,您只是绑定一个引用并获取构造函数中变量的地址,这不会产生不确定的值,并且是有效的。另一方面,following self-initialization example from the draft C++11 standard:

int x = x ;

确实引起了未定义行为。

活动问题453: 引用可能只绑定到“有效的”对象 似乎也是相关的,但仍然是打开的。最初提议的语言与缺陷报告363一致。

调用构造函数的位置是为待对象分配内存的位置。此时,该位置上不存在任何对象(或者可能存在一个具有简单析构函数的对象)。此外,this指针引用该内存,并且内存正确对齐。

由于它是分配和对齐的内存,我们可以使用 Foo类型的左值表达式(即 Foo&)来引用它。我们可以使用 没有进行从左值到右值的转换。只有在输入构造函数体之后才允许这样做。

在这种情况下,代码只是尝试在构造函数体内打印 &bar。在这里打印 bar.member甚至是合法的。因为已经输入了构造函数体,所以存在一个 Foo对象,并且可以读取它的成员。

这给我们留下了一个小细节,那就是名称查找。在 Foo foo(foo)中,第一个 foo在作用域中引入名称,因此第二个 foo引用刚声明的名称。这就是为什么 int x = x是无效的,但是 int x = sizeof(x)是有效的。