将C++对象传递到自己的构造函数中是否合法?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (29)

我惊讶地意外发现以下:

#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...
}

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

提问于
用户回答回答于

构造函数在内存分配给待处理对象的点处调用。此时,该位置不存在任何对象(或可能是具有微不足道的析构函数的对象)。此外,this指针指的是内存和内存正确对齐。

由于它被分配并对齐内存,所以我们可以使用Foo类型(ie Foo&)的左值表达式来引用它。我们可能没有做的是有一个左值到右值的转换。只有在构造函数体输入后才允许。

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

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

用户回答回答于

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

说明:

如果是这样,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段表明这里的参考文献是有效的。允许在完全初始化之前获取类对象的地址,并且只要引用可以直接绑定,就可以将其作为参数传递给引用参数。除了在printf中未能将指针转换为void *作为%p之外,这些示例符合标准。

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

类似地,在对象的生命周期开始之前,但是在对象将占用的存储之后,或者在对象的生命周期结束之后并且在重新使用或释放​​该对象占用的存储之前,任何引用的对象的glvalue可以使用原始对象,但仅限于有限的方式。正在建造或破坏的物体见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'在其初始化中使用时未初始化[-Winitinitialized]

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

int x = x ;

会调用未定义的行为。

扫码关注云+社区