在码代码时会经常遇到 stl 容器类的传递,stl 类内数据量较大时,拷贝时会耗费大量资源,需要避免,所以需要了解何时会调用拷贝构造函数.
网上看到一种答案:
拷贝构造函数调用的几种情况:
- 当用类的一个对象去初始化该类的另一个对象(或引用)时系统自动调用拷贝构造函数实现拷贝赋值。
- 若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数。
- 当函数的返回值是类对象时,系统自动调用拷贝构造函数。
为验证正确性,以下做了个实验.
# 1. 拷贝构造函数调用时机
# (i). 代码及输出
为了确认 c++ 中拷贝构造函数的调用时机,执行以下代码:
#include <iostream> | |
#include <vector> | |
using namespace std; | |
class Copy{ | |
public: | |
int val = 0; | |
Copy(){ | |
cout<<"construct"<<endl; | |
} | |
~Copy(){} | |
Copy(const Copy& copy){ | |
cout<<"copy"<<endl; | |
} | |
}; | |
void asParam(Copy copy){ | |
} | |
Copy returnCopy(){ | |
Copy copy; | |
cout<<" return:"<<endl<<" "; | |
return copy; | |
} | |
int main(int argc, const char * argv[]) { | |
Copy copy; | |
cout<<"align:"<<endl<<" "; | |
Copy copy1 = copy; | |
cout<<"init:"<<endl<<" "; | |
Copy copy2 = Copy(copy); | |
cout<<"asParam:"<<endl<<" "; | |
asParam(copy); | |
cout<<"in container:"<<endl; | |
vector<Copy> copys; | |
cout<<" push_back:"<<endl<<" "; | |
copys.push_back(copy); | |
cout<<" copy container:"<<endl<<" "; | |
vector<Copy> copys1 = copys; | |
cout<<"returnCopy:"<<endl<<" "; | |
returnCopy(); | |
} |
得到输出:
construct
align:
copy
init:
copy
asParam:
copy
in container:
push_back:
copy
copy container:
copy
returnCopy
construct
return:
# (ii). 结论
由以上输出,再对照网上找到的答案:
- 当用类的一个对象去初始化该类的另一个对象(或引用)时系统自动调用拷贝构造函数实现拷贝赋值。
- 若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数。
- 当函数的返回值是类对象时,系统自动调用拷贝构造函数。
可以得到:
- 用一个对象初始化时会调用拷贝构造函数,包括用 "=" 赋值和 Copy (copy) 这样用类作为构造函数的参数的初始化.
- 作为形参时,也会调用到拷贝构造函数.
- 在作为函数的返回值时, 并不会调用拷贝构造函数.
# 2. 类作为函数的返回值
可以看到在作为函数返回值时,并不会调用拷贝构造函数,但局部的对象在栈中,函数结束后会随之销毁,不可能返回函数中的局部对象。为此,用以下代码再次实验:
# (i). 代码及输出
#include <iostream> | |
#include <vector> | |
using namespace std; | |
class Copy{ | |
public: | |
int val = 0; | |
Copy(){ | |
cout<<"construct"<<endl; | |
} | |
Copy(int i):val(i){} | |
~Copy(){} | |
Copy(const Copy& copy){ | |
cout<<"copy"<<endl; | |
} | |
}; | |
Copy returnCopy(){ | |
Copy copy(5); | |
cout<<"local: val "<<copy.val<<" ,address "<<©<<endl; | |
return copy; | |
} | |
int main(int argc, const char * argv[]) { | |
Copy copy = returnCopy(); | |
cout<<"return: val "<<copy.val<<" ,address "<<©<<endl; | |
} |
输出:
local: val 5 ,address 0x7ffeefbff578
return: val 5 ,address 0x7ffeefbff578
# (ii). 结论
可以看到,很神奇的是,返回的居然是局部对象,有点超出我的理解了.
然后经过资料查阅,发现结论是这样的:
按照正常情况在 return 时是会发生一次拷贝,但是 g++ 会有一种 RVO (return value optimization) 的技术,在编译时会发生 Copy elision(复制省略).
大致意思就是避免不必要的拷贝,如这里,使用临时变量为同类型的变量赋值时,转换为直接初始化复制到的变量,因此这里不会调用到拷贝构造函数.
# 3. 总结
c++ 相比 java 等语言少了一层 jvm, 本身语言更偏底层了,但很多内存的管理分配还是值得深究.
而且通过这次发现编译器功能真的十分强大,能给我们的程序带来很多意想不到的优化。但这样的同时,也会给我们的程序带来更多变数,需要程序员更深入了解 c++ 与编译器的一些优化策略,从而更好地控制我们的代码与程序.
未来仍需努力~