请考虑以下代码:
class Foo {
public:
explicit Foo(double) {}
};
Foo * test();
Foo * test() {
return new Foo(Foo(1.0)); // (1)
}
我的问题涉及第(1)行。这非常类似于我花了一些时间来追踪的错误。由于复制/粘贴错误,我没有注意到该类型已被指定两次。显而易见的是:
return new Foo(1.0);
有趣的是,这种变化似乎也可以免费编译:
return new Foo(Foo(Foo(Foo(1.0))));
为什么这些示例在没有警告的情况下编译,即使使用-Wall -Weverything
标志?为什么Foo::Foo(double)
接受Foo的实例作为有效的double
参数?这是运营商新的一些特殊行为吗?
我的原始代码位于更大的上下文中,并使用两个基于LLVM-3的编译器进行了测试。编译时都没有警告或错误。有一个,代码实际上按照我的预期运行,实际上我没有意识到有一段时间有一个bug。另一方面,Foo的实例表现得非常奇怪 - 我无法正确描述它 - 就好像后来的返回指针的副本“神奇地”变成了与原始值不同的值,导致两个合作之间的状态不匹配应该保存与共享Foo等效指针的对象,但由于某种原因在赋值后持有不同的值。在我明白这里发生了什么之前,它似乎真的很奇怪!
有趣的是,以下两个编译器都会编译:
class Foo { public: explicit Foo(double) {} };
class Bar { public: explicit Bar(double) {} };
Foo * testFoo() { return new Foo(Foo(1.0)); }
Bar * testBar() { return new Bar(Bar(1.0)); }
但是以下版本没有:
Foo * testFooBar() { return new Foo(Bar(1.0)); }
答案 0 :(得分:7)
编译器会自动生成复制构造函数Foo(const Foo&)
,并可能根据确切的版本/设置Foo(Foo&&)
移动构造函数。这与operator new或任何指针魔法无关。您的代码只是调用编译器为您定义的完全正常的复制/移动构造函数。这就是全部。此行为由Standard强制执行。不可复制的类(至少在标准的原始版本中)几乎毫无价值。
如果您不想自动生成复制构造函数,通常的技术是将它们定义为已删除或私有。
另请注意,在某些情况下,编译器有权从程序中实际删除整个对象,即使通常它不应该。如果你在Foo构造函数中使用指向foo的set进行hijinks,你必须在所有构造函数和析构函数中严格处理它们,这意味着必须编写自己的复制/移动构造函数,析构函数和赋值运算符,否则你会来编译器遗留一个对象时的一个cropper。
答案 1 :(得分:2)
您正在观察的构造函数是复制构造函数。每个类都有一个复制构造函数。如果您未在类定义中声明任何复制构造函数,则会为您隐式声明隐式声明复制构造函数。
隐式声明的复制构造函数的特定签名取决于您的类定义,即基础和成员 - 通常是X::X(X const &)
形式,但它可能是X::X(X &)
或{{1如果有必要的话。例外规格尽可能严格。隐式声明的复制构造函数的定义通常包括复制基础和成员,但在某些情况下可能被定义为已删除(例如,如果您声明移动构造函数)。
(在C ++的未来版本中,如果声明一个副本或移动赋值运算符,则隐式声明的复制构造函数将被定义为已删除。在C ++ 11和C ++ 14中,它是默认的。)