读书人

复制构造函数的诡异行为哪位C++高手

发布时间: 2012-03-21 13:33:15 作者: rapoo

复制构造函数的诡异行为,哪位C++高手可以解释一下?
C++ Primer(4 edition) 中文版P407上说:“复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。”但我的实验结果显示是没有调用复制构造函数。
我做了个实验(实验1),首先写了一个简单的类,里面提供一个默认构造函数,一个接受单个形参的构造函数,一个复制构造函数和一个=重载函数,在上面每个函数中都打印一个消息,提示这个函数正在被调用。
接着在主函数使用这个类,使用复制初始化这个类的对象。按照书上说的,显示结果应该是先调用单形参的构造函数创建临时对象,输出一个提示,然后调用复制构造函数,输出一个提示。但结果只有一个提示!显示的是只调用了单形参的构造函数直接创建了对象,并没有临时对象的存在。结果很诡异!
既然没有调用复制构造函数,我又做了个实验(实验2),把这个复制构造函数改成private的,其他地方没有变,做了同样的实验,编译就不通过了,错误提示是我的主函数调用了复制构造函数。结果是不是很诡异?
问题:C++在复制初始化的时候到底有没有创建临时对象?如果有,为什么会出现实验1的结果;如果没有为什么出现实验2的结果?

实验1的类定义代码如下:

C/C++ code
class Employee{public:    Employee();    Employee(std::string sname);    // copy constructor    Employee(const Employee &old);    Employee& operator=(const Employee &old);    int getID() const { return id; }    std::string getName() const { return name; }private:    static int newid;    int id;    std::string name;};


实验2的类定义代码如下:
C/C++ code
class Employee{public:    Employee();    Employee(std::string sname);    Employee& operator=(const Employee &old);    int getID() const { return id; }    std::string getName() const { return name; }private:    // copy constructor    Employee(const Employee &old);    static int newid;    int id;    std::string name;};


实验的类实现代码如下:
C/C++ code
int Employee::newid = 1;Employee::Employee() : id(newid){    std::cout << "In default constructor, id = " << id << std::endl;    ++newid;}Employee::Employee(std::string sname) : id(newid), name(sname){    std::cout << "In single parameter constructor, id = " << id << std::endl;    ++newid;}// copy constructorEmployee::Employee(const Employee &old) : id(newid), name(old.getName()){    std::cout << "In copy constructor, id = " << id << std::endl;    ++newid;}Employee& Employee::operator=(const Employee &old){    std::cout << "In aissign function, id = " << id << std::endl;    name = old.getName();    return *this;}


主函数使用类的代码如下:
C/C++ code
int main(){    string name = "adu";    Employee em5 = name;    return 0;}


实验1结果:
In single parameter constructor, id = 1

实验2结果:
error: ‘Employee::Employee(const Employee&)’ is private

[解决办法]
因为优化的结果
[解决办法]
Employee(std::string sname); 从参数类型向类类型转换。用explicit禁止。
Employee e; // 默认构造函数
Employee e1 = e; // 复制初始化
Employee e2; // 默认构造函数
e2 = e; // 赋值初始化
实验2 VS2010正常运行。
[解决办法]
探讨
Employee(std::string sname); 从参数类型向类类型转换。用explicit禁止。
Employee e; // 默认构造函数
Employee e1 = e; // 复制初始化
Employee e2; // 默认构造函数
e2 = e; // 赋值初始化
实验2 VS2010正常运行。

[解决办法]
不诡异,编译器可以优化掉不必要的临时变量。
------解决方案--------------------


你的构造函数:
Employee();
Employee(std::string sname);
Employee(const Employee &old);
你的拷贝函数:
Employee& operator=(const Employee &old);

实验1:实例化对象通过 但是执行拷贝函数时 类型不匹配 必须为Employee类型
解决方法:
1、只用构造函数而不用拷贝函数

C/C++ code
Employee em5 = name;改成Employee em5(name);               //用到了Employee(std::string sname)这个构造函数
[解决办法]
这句话,印象中primer确实有



string str="ihello"; 就说这句代码

首先编译器: 讲ihello这个字符串创建了一个 临时的 string对象,然后


用这个临时对象 进行赋值拷贝了了str

只不过编译器优化了,看不出来


说的不对,请指教






探讨

要看这些底层怎么实现的要看<深度探索C++对象模型>,复制构造函数的参数只能是引用参数或指针参数,要
然会无限制的调用复制构造函数,最后导致栈溢出,所以如果你将复制构造函数的参数写成传值的话一般编译器的

会报错的,所以你的这句话“ 中文版P407上说:“复制初始化首先使用指定构造函数创建一个临时对象,

然后用复制构造函数将那个临时对象复制到正在创建的对象。””就不对,复制初始……

[解决办法]
很多理论上需要先执行"构造函数"创建一个临时对象,再执行"复制构造函数"的地方,都会被编译器优化,省去"复制构造函数"的步骤,也就是省去临时对象。

比如下面的函数,理论上也是应该在函数内部先创建一个临时局部对象,再用"复制构造函数"构造a,但实际上只有一次普通构造函数。

Object f()
{
...
return Object();
}

Object a = f();

[解决办法]
探讨

引用:

要看这些底层怎么实现的要看<深度探索C++对象模型>,复制构造函数的参数只能是引用参数或指针参数,要不

然会无限制的调用复制构造函数,最后导致栈溢出,所以如果你将复制构造函数的参数写成传值的话一般编译器的

会报错的,所以你的这句话“ 中文版P407上说:“复制初始化首先使用指定构造函数创建一个临时对象,

然后用复制构造函数将那……
1.我这个是引用原文的话,而且参数是用引用传参,这个是没有问题的。
2.复制初始化临时创建的是指针?这是哪里看的?可以怎么做实验验证吗?
3.我问的是关于构造函数,不是一般函数传参。

[解决办法]
我觉得这是编译器优化的结果
C++ Primer 4中文版407页还说过:“当情况许可时,可以允许编译器跳过复制构造函数直接创建对象,但编译器没有义务这样做。”
可见编译器跳过复制构造函数直接对象的情况是存在的。
[解决办法]
优化过了





探讨

我来发给VS 2010的反汇编吧

Assembly code

string name = "adu";
01351D80 push offset string "adu" (13598C4h)
01351D85 lea ecx,[ebp-34h]
01351D88 call std::basic_string<char,……

[解决办法]
探讨

没试过反汇编,果然是大牛,有没有试过反汇编第二个实验?

引用:

我来发给VS 2010的反汇编吧

Assembly code

string name = "adu";
01351D80 push offset string "adu" (13598C4h)
01351D85 lea ecx,[ebp-……

读书人网 >C++

热点推荐