将一个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所覆盖:从self初始化类,它说:
如果是,UDT的自初始化的语义是什么?例如
#include <stdio.h> struct A { A() { printf("A::A() %pn", this); } A(const A& a) { printf("A::A(const A&) %p %pn", this, &a); } ~A() { printf("A::~A() %pn", this); } }; int main() { A a=a; }
可以编译并输出:
A::A(const A&) 0253FDD8 0253FDD8 A::~A() 0253FDD8
,分辨率为:
(基本3.8。第6段表明这里的引用是有效的。允许在类对象完全初始化之前获取它的地址,并且允许将其作为参数传递给引用形参,只要引用可以直接绑定。除了在printfs中没有将%p的指针强制转换为void *外,这些示例都符合标准。
部分3.8
[基本]的完整报价。来自c++ 14标准草案如下:
类似地,在对象的生命周期开始之前,但在对象将要占用的存储空间已经被分配,或者在对象的生命周期已经结束,并且在对象所使用的存储空间之前被占用的对象被重用或释放,任何指向原物可以使用,但使用方式有限。对于一个对象正在建造或破坏中,见12.7。否则,这样的glvalue引用分配存储(3.7.4.2),并使用不依赖于其值的Glvalue是定义良好的。这个项目如果:
有未定义的行为
左值到右值的转换(4.1)应用于这样的glvalue,
glvalue用于访问非静态数据成员或调用类的非静态成员函数对象,或者
glvalue绑定到虚基类(8.5.3)的引用,或者
glvalue用作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)
是有效的。
如其他答案所述,只要在初始化之前不使用对象的值,就可以用对象本身初始化。您仍然可以将对象绑定到引用或获取其地址。但除了它是有效的这一事实之外,让我们探索一个用法示例。
下面的例子可能是有争议的,你当然可以提出许多其他的想法来实现它。然而,它给出了这个奇怪的c++属性的一种有效用法,你可以将一个对象传递给它自己的构造函数。
class Employee {
string name;
// manager may change so we don't hold it as a reference
const Employee* pManager;
public:
// we prefer to get the manager as a reference and not as a pointer
Employee(std::string name, const Employee& manager)
: name(std::move(name)), pManager(&manager) {}
void modifyManager(const Employee& manager) {
// TODO: check for recursive connection and throw an exception
pManager = &manager;
}
friend std::ostream& operator<<(std::ostream& out, const Employee& e) {
out << e.name << " reporting to: ";
if(e.pManager == &e)
out << "self";
else
out << *e.pManager;
return out;
}
};
现在使用自身初始化对象:
// it is valid to create an employee who manages itself
Employee jane("Jane", jane);
实际上,对于Employee类的给定实现,用户别无选择,只能初始化有史以来创建的第一个Employee,并将其自身作为自己的管理器,因为还没有其他Employee可以传递。在某种程度上,这是有道理的,因为第一个员工应该管理自己。
代码:http://coliru.stacked-crooked.com/a/9c397bce622eeacd
- 这个C++编译器优化(在自身的实例上调用对象自己的构造函数)的名称是什么,它是如何工作的?
- 派生类是从基类继承 v 指针并仅使用它,还是也有自己的 v 指针?
- 为什么存储在数组中的地址值与其自己的地址相同,它应该指向某个不同的地址(&arr[0])?
- 访问另一个类(系统)的非静态字段,就好像它是我自己的字段一样 - 优雅地
- 是否可以使用TensorFlow训练自己的模块,然后在OpenCV中使用它进行检测
- 张量类,它有自己的存储,但也可以映射外部指针
- C++ std::函数,返回它自己的类型(再次递归类型)
- 为什么我的指针在 main 中取消引用后具有不同的值,而不是在我自己的自定义函数中取消引用它时?
- 它自己类的c++类常量
- 使用std::vector::Assign()将矢量的一部分分配给它自己
- 是否可以将项目直接从Visual Studio导出到它自己的文件夹中?
- C++:一个类可以有它自己的类型的对象吗?
- 在C++中,我是否需要创建自己的堆栈或队列类,或者是否可以从某个地方导入它
- 在自己的.cpp中定义一个结构,然后在main中使用它
- 一个类是如何从一个专门针对它自己的模板派生出来的
- 为什么派生类调用基类运算符而不是它自己的运算符
- 是否有任何已知的技术可以使用将 DLL 插件分离到它自己的进程中?
- 我可以为c#制作自己的JIT解释器编译器并在Visual Studio中使用它吗?
- 只有 1 种方法的类,让它处理自己的互斥锁是否安全?
- 当我读取文件时,由于某种原因,它自己改变了文件中的位置