繁体   English   中英

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

[英]Is passing a C++ object into its own constructor legal?

我很惊讶地意外发现以下工作:

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

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

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

请注意,我不是在问这是否有用或一个好主意; 我只是想了解更多关于课程的信息。

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

缺陷报告 363: Initialization of class from self涵盖了这一点,其中说:

如果是这样,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 中的 %p 的指针强制转换为 void * 之外,这些示例均符合标准。

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

类似地,在对象的生命周期开始之前但是在对象将占用的存储空间已经分配之后,或者在对象的生命周期结束之后并且在对象占用的存储空间被重用或释放之前,任何引用可以使用原始对象,但只能以有限的方式使用。 对于正在建造或销毁的物体,见 12.7。 否则,这样的泛左值指的是分配的存储 (3.7.4.2),并且使用不依赖于其值的泛左值的属性是明确定义的。 在以下情况下,程序具有未定义的行为:

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

  • 泛左值用于访问非静态数据成员或调用对象的非静态成员函数,或

  • 泛左值绑定到对虚拟基类 (8.5.3) 的引用,或

  • 泛左值用作 dynamic_cast (5.2.7) 的操作数或 typeid 的操作数。

我们不会对foo做任何属于上述项目符号定义的未定义行为的事情。

如果我们用 Clang 尝试这个,我们会看到一个不祥的警告( see it live ):

警告:变量 'foo' 在它自己的初始化中使用时未初始化 [-Wuninitialized]

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

int x = x ;

确实会调用未定义的行为。

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM