C++ 正在通过一个C++;对象转换为其自己的构造函数是否合法?
我意外地发现以下方法有效:C++ 正在通过一个C++;对象转换为其自己的构造函数是否合法?,c++,class,constructor,language-lawyer,undefined-behavior,C++,Class,Constructor,Language Lawyer,Undefined Behavior,我意外地发现以下方法有效: #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
#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...
}
#包括
int main(int argc,字符**argv)
{
结构Foo{
Foo(Foo&bar){
std::cout这不是未定义的行为。虽然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;
}
决议是:
3.8[基本生活]第6段指出此处的引用是有效的。允许在类对象完全初始化之前获取其地址,并且允许将其作为参数传递给引用参数,只要引用可以直接绑定。除了未能将printfs中%p的指针强制转换为void*之外,这些PLE符合标准
C++14标准草案第3.8节[basic.life]的完整引用如下:
类似地,在对象的生存期开始之前但在
对象将占用的存储已分配,或者
对象的生存期已结束,在
对象占用被重用或释放,任何引用
可以使用原始对象,但只能以有限的方式使用。对于对象
正在施工或破坏中,见12.7。否则,该值
指已分配的存储(3.7.4.2),并使用
不依赖于其值的glvalue是定义良好的
具有未定义的行为,如果:
- 将左值到右值的转换(4.1)应用于该glvalue
- glvalue用于访问的非静态数据成员或调用的非静态成员函数
反对,或
- glvalue绑定到对虚拟基类(8.5.3)的引用,或
- glvalue用作动态_转换(5.2.7)的操作数或typeid的操作数
我们没有对foo
进行任何属于上述项目符号定义的未定义行为的操作
如果我们尝试使用叮当声,我们会看到一个不祥的警告():
警告:变量“foo”在其自身初始化中使用时未初始化[-Wuninitialized]
这是一个有效的警告,因为。但是,在这种情况下,您只是绑定一个引用,并在构造函数中获取变量的地址,这不会产生一个不确定的值,并且是有效的。另一方面:
调用未定义的行为
也似乎相关,但仍然是开放的。最初建议的语言与缺陷报告363一致。在为要调用的对象分配内存的点调用构造函数。在该点上,该位置不存在任何对象(或者可能是具有普通析构函数的对象)。此外,此
指针指向该内存,并且内存已正确对齐
由于它是已分配和对齐的内存,我们可以使用Foo
类型的左值表达式(即Foo&
)来引用它。我们可能还没有做的是左值到右值的转换。只有在输入构造函数体后才允许这样做
在这种情况下,代码只是试图在构造函数体内部打印&bar
。在这里打印bar.member
甚至是合法的。由于已输入构造函数体,因此存在Foo
对象,可以读取其成员
这给我们留下了一个小细节,那就是名称查找。在Foo-Foo(Foo)
中,第一个Foo
在范围中引入名称,第二个Foo
因此引用刚才声明的名称。这就是为什么int x=x
无效,但int x=sizeof(x)
是有效的。@ShafikYaghmour为什么要删除你的答案?只需添加一个[basic.life]p6的引号作为限制。是的,这很好;它本质上与在构造函数中使用这个
是一样的,有着所有的陷阱。这不是更像size\t x=sizeof(x)
?在分配内存的位置调用对象的构造函数(来自未指定的源)。只要您只依赖于存储的属性,而不依赖于任何值的解释,事情就应该是安全的。@mAlters有一个微妙的问题,就是在将对象绑定到引用之前,即在构造函数调用之前,是否分配了存储。Shafik,根据您的建议,我暂时不接受您的回答,虽然对我来说已经够深了:)啊,好吧:)(不知何故,我没有想到这一点,认为这是理所当然的。)尽管如此,我认为标准中缺少了一个小细节。@dyp:确实没有精确指定,但必须在第一个基类构造函数开始执行之前对其进行排序。您无法确定之前的顺序。谢谢Shafik!在堆栈溢出问题上闲逛的人,以及站点提出正确问题的能力右眼的前方总是让我惊讶不已!对不起,我说的是原来的帖子/问题。无论如何,再想想,我可能误解了“构建”部分。我以为OP意味着我们将构造对象传递到该对象的构造函数中,而实际上我们将未初始化的对象传递到构造函数中。但在使用g++4.8编译int x=x
时,我没有收到任何错误。无效语句应该会出错,对吗?@cbinder:应该,但不幸的是不会。
A::A(const A&) 0253FDD8 0253FDD8
A::~A() 0253FDD8
int x = x ;