前言
在构造函数和拷贝构造函数这一块知识点上迷糊了很久,因此做个深入的探究。
参考链接:
C++:类的默认成员函数------拷贝构造函数&&赋值运算符重载
编译环境:
编译环境 | 编译器 |
---|---|
win10专业版 22H2 | MinGW 64bit: g++ 11.2.0 |
构造函数与拷贝构造函数的区别
区别 | 构造函数 | 拷贝构造函数 |
---|---|---|
调用 | 创建对象时,使用构造函数的默认值进行初始化 | 创建对象时,使用其他对象的值进行初始化 |
输入参数 | 无参构造函数、有参构造函数 | 参数为该类的一个引用或常量引用 |
拷贝构造函数的使用条件:
1.通过另一个对象进行初始化
2.返回对象
3.当对象作为参数传递给函数
探究使用拷贝构造函数:
代码如下:
#include <iostream>
#include <string>
#include <vector>
class TestDemo
{
public:
int a;
char c;
// 普通的无参构造函数
TestDemo() {
this->a = 0;
this->c = 'a';
std::cout << "Use Constructor, Input: None, " << this << std::endl;
}
// 拷贝构造函数,参数类型是该类的一个引用或常量引用
TestDemo(const TestDemo &other) {
this->a = other.a;
this->c = other.c;
std::cout << "Use Copy Constructor, " << this << std::endl;
}
~TestDemo() {
std::cout << "Use Destructor, " << this << std::endl;
}
};
// 参数为引用时,不会调用拷贝构造函数
void passQuoteFunc(TestDemo &val) {
std::cout << __func__ << ", " << &val << std::endl;
}
// 参数为值时,会调用拷贝构造函数
void passValueFunc(TestDemo val) {
std::cout << __func__ << ", " << &val << std::endl;
}
TestDemo retFunc1() {
TestDemo ret;
std::cout << __func__ << ", " << &ret << std::endl;
return ret;
}
TestDemo g_demo2;
TestDemo retFunc2() {
std::cout << __func__ << ", " << &g_demo2 << std::endl;
return g_demo2;
}
int main(int argc, char *argv[])
{
TestDemo test1; // 调用普通构造函数
std::cout << "[Creat: test1] " << &test1 << std::endl;
TestDemo test2 = test1; // 1.通过另一个对象进行初始化,调用拷贝构造函数
std::cout << "[Creat: test2] " << &test2 << std::endl;
TestDemo test3;
test3 = test1; // 对象已存在,因此不调用拷贝构造函数
std::cout << "[Creat: test3] " << &test3 << std::endl;
TestDemo test4 = retFunc1(); // 2.当对象作为参数传递给函数,理论上应调用拷贝构造函数,实际由编译器决定
std::cout << "[Creat: test4] " << &test4 << std::endl;
TestDemo test5 = retFunc2(); // 2.当对象作为参数传递给函数,理论上应调用拷贝构造函数,实际由编译器决定
std::cout << "[Creat: test5] " << &test5 << std::endl;
passQuoteFunc(test1);
std::cout << "[Use passQuoteFunc]" << std::endl;
passValueFunc(test1); // 3.当对象作为参数传递给函数
std::cout << "[Use passValueFunc]" << std::endl;
return 0;
}
运行结果如下:
Use Constructor, Input: None, 0x7ff691a27030
Use Constructor, Input: None, 0xcdf45ffb50
[Creat: test1] 0xcdf45ffb50
Use Copy Constructor, 0xcdf45ffb48
[Creat: test2] 0xcdf45ffb48
Use Constructor, Input: None, 0xcdf45ffb40
[Creat: test3] 0xcdf45ffb40
Use Constructor, Input: None, 0xcdf45ffb38
retFunc1, 0xcdf45ffb38
[Creat: test4] 0xcdf45ffb38
retFunc2, 0x7ff691a27030
Use Copy Constructor, 0xcdf45ffb30
[Creat: test5] 0xcdf45ffb30
passQuoteFunc, 0xcdf45ffb50
[Use passQuoteFunc]
Use Copy Constructor, 0xcdf45ffb58
passValueFunc, 0xcdf45ffb58
Use Destructor, 0xcdf45ffb58
[Use passValueFunc]
Use Destructor, 0xcdf45ffb30
Use Destructor, 0xcdf45ffb38
Use Destructor, 0xcdf45ffb40
Use Destructor, 0xcdf45ffb48
Use Destructor, 0xcdf45ffb50
Use Destructor, 0x7ff691a27030
运行结果与预期不符:
分析运行结果后,可能会有有疑问:
为什么变量test4和变量test5的运行结果不一样,变量test4调用的是普通的构造函数,变量test5的调用的是拷贝构造函数。
预期的运行结果应该是变量test4和变量test5都调用拷贝构造函数。
结果:
在某些情况下,编译器可能会对代码进行优化,以避免调用拷贝构造函数。在现代C++中,编译器引入了所谓的NRVO(Named Return Value Optimization)和RVO(Return Value Optimization)优化,这意味着在某些情况下,编译器会避免不必要的对象拷贝。
拷贝构造函数相对于普通构造函数可能会带来额外的开销:
1.内存分配和数据复制:拷贝构造函数通常会涉及到内存分配和数据复制,这可能会在一定程度上增加开销,尤其是当对象包含大量数据时。
2.执行时间:拷贝构造函数的执行时间可能比普通构造函数长,因为它需要复制对象的状态,这可能包括成员变量、指针指向的数据等。
3.锁定资源:如果对象拥有某些资源(比如文件句柄、网络连接等),拷贝构造函数可能需要执行额外的操作来确保资源的正确共享或释放。
4.析构函数的调用:在拷贝构造函数中,必须确保在复制旧对象的值到新对象之后,旧对象的资源得到正确的释放。这可能需要额外的操作。