我用一个包含自定义构造函数,析构函数,复制构造函数和赋值运算符的示例类编写了一个小测试程序。当我意识到复制构造函数根本没有被调用时,我感到很惊讶,即使我使用我的类的返回值和像Object o1; Object o2(o1);
这样的行来实现函数
innerclass.hpp:
#include <iostream>
class OuterClass
{
public:
OuterClass()
{
std::cout << "OuterClass Constructor" << std::endl;
}
~OuterClass()
{
std::cout << "OuterClass Destructor" << std::endl;
}
OuterClass(const OuterClass & rhs)
{
std::cout << "OuterClass Copy" << std::endl;
}
OuterClass & operator=(const OuterClass & rhs)
{
std::cout << "OuterClass Assignment" << std::endl;
}
class InnerClass
{
public:
InnerClass() : m_int(0)
{
std::cout << "InnerClass Constructor" << std::endl;
}
InnerClass(const InnerClass & rhs) : m_int(rhs.m_int)
{
std::cout << "InnerClass Copy" << std::endl;
}
InnerClass & operator=(const InnerClass & rhs)
{
std::cout << "InnerClass Assignment" << std::endl;
m_int = rhs.m_int;
return *this;
}
~InnerClass()
{
std::cout << "InnerClass Destructor" << std::endl;
}
void sayHello()
{
std::cout << "Hello!" << std::endl;
}
private:
int m_int;
};
InnerClass innerClass()
{
InnerClass ic;
std::cout << "innerClass() method" << std::endl;
return ic;
}
};
innerclass.cpp:
#include "innerclass.hpp"
int main(void)
{
std::cout << std::endl << "1st try:" << std::endl;
OuterClass oc;
OuterClass oc2(oc);
oc.innerClass().sayHello();
std::cout << std::endl << "2nd try:" << std::endl;
OuterClass::InnerClass ic(oc.innerClass());
ic = oc.innerClass();
}
输出:
1st try:
OuterClass Constructor
OuterClass Copy
InnerClass Constructor
innerClass() method
Hello!
InnerClass Destructor
2nd try:
InnerClass Constructor
innerClass() method
InnerClass Constructor
innerClass() method
InnerClass Assignment
InnerClass Destructor
InnerClass Destructor
OuterClass Destructor
OuterClass Destructor
经过一些研究后,我读到无法保证编译器将使用明确定义的复制构造函数。我不明白这种行为。那么为什么复制构造函数甚至存在呢,如果我们不知道它被调用了?编译器如何决定它是否使用它?
或者,更好的是,有没有办法强制编译器使用自定义的复制构造函数?
答案 0 :(得分:6)
为了完整性与其他答案,标准允许编译器在某些情况下省略复制构造函数(其他答案称为“返回值优化”或“命名返回值优化” - RVO / NRVO):< / p>
12.8复制类对象,第15段(C ++ 98)
每当使用复制构造函数复制临时类对象,并且此对象和副本具有相同的cv-unqualified类型时,允许实现将原始和副本视为引用同一对象的两种不同方式并且根本不执行副本,即使类复制构造函数或析构函数具有副作用。对于具有类返回类型的函数,如果return语句中的表达式是本地对象的名称,并且本地对象的cv-unqualified类型与函数返回类型相同,则允许实现省略创建即使类复制构造函数或析构函数具有副作用,临时对象仍保存函数返回值。在这些情况下,如果原始和副本在没有优化的情况下被销毁,对象就会被销毁。
因此,在innerClass()
方法中,您可能认为在返回时调用的复制构造函数可以被优化掉:
InnerClass innerClass() {
InnerClass ic;
std::cout << "innerClass() method" << std::endl;
return ic; // this might not call copy ctor
}
答案 1 :(得分:4)
您不应该依赖于在特定情况下调用(或不调用)的复制构造函数来设计您的类。允许编译器在各种地方忽略或添加复制构造函数调用,你真的不想跟踪它们。
至于为什么需要一个 - 好吧,编译器可能会认为它需要一个副本,而复制构造函数就是它用来做的。复制构造函数调用被忽略的优点是性能 - 复制通常是非常昂贵的操作。
答案 2 :(得分:4)
我同意Neil,你不应该编写一个依赖于被调用的复制构造函数的类。即因为编译器可以执行诸如“命名返回值优化”(link)之类的操作,这在许多返回值方案中完全避免了复制构造函数。为了强制调用复制构造函数,您需要编写大量旨在“欺骗”C ++编译器的代码。不是个好主意。
在特定情况下,如果要强制调用复制构造函数,则可以执行显式调用。
Object SomeFunc() {
Object o1 = ...
return Object(o1);
}
答案 3 :(得分:2)
这是问题吗?
OuterClass(const OuterClass & rhs)
{
std::cout << "OuterClass Constructor" << std::endl;
==>
std::cout << "OuterClass Copy Constructor" << std::endl;
}
OuterClass & operator=(const OuterClass & rhs)
{
std::cout << "OuterClass Constructor" << std::endl;
==>
std::cout << "OuterClass Assignment operator" << std::endl;
}
复制粘贴错误!
我建议你调试代码一次,看看究竟发生了什么。调试确实可以帮助您找到问题。
内部课程的编辑:
正如其他人已经指出的那样,这是名称返回值优化(NRVO)的情况。
InnerClass innerClass()
{
InnerClass ic;
std::cout << "innerClass() method" << std::endl;
return ic;
}
编译器可以将函数转换为
void innerClass( InnerClass &namedResult)
{
std::cout << "innerClass() method" << std::endl;
}
因此,它消除了类对象的返回值以及调用类复制构造函数的需要。
请通过以下两个链接了解有关NRVO的更多信息:
答案 4 :(得分:0)
Object o1();
不创建任何对象,而是定义函数原型,函数名称为o1,void参数,返回类型为Object。您需要发布一些代码才能找到实际问题。
答案 5 :(得分:0)
发布一些代码。我认为你使用了一些不正确的语法。
复制构造函数必须具有以下签名:
MyObject(const MyObject&)
然后,您可以看到是否使用以下代码调用了复制构造函数:
MyObject m1;
MyObject m2(m1);
您不能使用MyObject m1();
作为函数声明。