传递一个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... } 

我将构造对象的地址传递给它自己的构造函数。 这看起来像来源层面的循环定义。 标准是否真的允许你在对象构造之前将一个对象传入一个函数,或者这个未定义的行为?

我想这并不奇怪,因为所有的类成员函数都有一个指向其类实例的数据的指针,作为一个隐式参数。 数据成员的布局在编译时是固定的。

请注意,我不是问这是否有用或好主意, 我只是在学习更多的课程。

这不是未定义的行为,尽pipefoo是未初始化的,您正在使用标准允许的方式。 在为对象分配空间之后,在完全初始化之前,允许以有限的方式使用空间。 绑定对该variables的引用并获取其地址是允许的。

这是由缺陷报告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段表示这里的引用是有效的。 允许在完全初始化之前获取类对象的地址,只要引用可以直接绑定,就可以将其作为parameter passing给引用参数。 除了在printf中未能将指针转换为void *作为%p,这些示例是标准符合的。

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

类似地,在对象的生命周期开始之前,但是在对象将要占用的存储之后,或者在对象的生命周期结束之后,在重新使用或释放​​该对象占用的存储之前,任何引用原始对象可以被使用,但是仅以有限的方式使用。 正在施工或破坏的物体见12.7。 否则,这样的glvalue是指分配的存储(3.7.4.2),并且使用不依赖于它的值的glvalue的属性是明确的。 该程序具有未定义的行为,如果:

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

  • glvalue用于访问非静态数据成员或调用对象的非静态成员函数

  • glvalue绑定到对虚拟基类(8.5.3)的引用,或者

  • glvalue用作dynamic_cast(5.2.7)的操作数或者作为typeid的操作数。

我们没有对foo进行任何处理,这些foo属于上面定义的未定义的行为。

如果我们用叮当试试这个,我们会看到一个不祥的警告( 见它现场 ):

警告:variables'foo'在初始化时被初始化[-Winitialized]

这是一个有效的警告,因为从未初始化的自动variables产生和不确定的值是未定义的行为,但在这种情况下,你只是绑定一个引用,并取得构造函数中的variables的地址不产生不确定的值,是有效的。 另一方面来自C ++ 11标准草案的以下自我初始化示例 :

 int x = x ; 

会调用未定义的行为。

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

构造函数在内存分配给待处理对象的地方被调用。 此时,在该位置不存在对象(或者可能是具有微不足道的析构函数的对象)。 而且, this指针指的是内存和内存正确alignment。

由于分配和alignment的内存,我们可以使用Footypes的左值expression式(即Foo& )来引用它。 我们可能没有做的是左值到右值的转换。 只有在构造函数体被input后才允许。

在这种情况下,代码只是试图在构造函数体内打印&bar 。 在这里打印bar.member甚至是合法的。 由于构造函数体已经被input,所以Foo对象存在并且其成员可以被读取。

这给我们留下了一个小细节,这就是名字查询。 在Foo foo(foo) ,第一个foo在范围中引入了名称,第二个foo因此返回到刚刚声明的名称。 这就是为什么int x = x是无效的,但是int x = sizeof(x)是有效的。