尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

C++拷贝构造与拷贝赋值运算符的区别详解

C++拷贝构造与拷贝赋值运算符的区别详解 1. 引言在C中拷贝构造和拷贝赋值运算符都是用于对象拷贝的重要成员函数但它们在调用时机、使用场景和实现细节上有着本质的区别。理解这两者的差异对于编写正确的、高效的C代码至关重要尤其是在涉及资源管理如动态内存、文件句柄等的类中。本文将详细解析拷贝构造函数和拷贝赋值运算符的区别并通过代码示例帮助读者深入理解。2. 基本概念2.1 拷贝构造函数拷贝构造函数用于创建一个新对象并用另一个同类型对象的值来初始化这个新对象。其函数签名通常为ClassName(const ClassName other);或者C11起ClassName(const ClassName other) default; // 显式默认 ClassName(ClassName other) delete; // 禁止移动构造2.2 拷贝赋值运算符拷贝赋值运算符用于将一个已存在对象的值赋给另一个已存在的对象。其函数签名通常为ClassName operator(const ClassName other);或者ClassName operator(const ClassName other) default;3. 核心区别对比对比维度拷贝构造函数拷贝赋值运算符调用时机创建新对象时已存在对象之间赋值时对象状态目标对象尚未构造目标对象已构造完成函数签名ClassName(const ClassName)ClassName operator(const ClassName)返回值无返回值构造函数返回ClassName支持链式赋值默认行为默认执行浅拷贝成员逐一拷贝默认执行浅拷贝成员逐一赋值自赋值检查不需要新对象不可能等于源对象需要obj obj是合法操作资源管理直接分配新资源先释放旧资源再分配新资源4. 调用场景示例4.1 拷贝构造函数的调用场景class MyClass { public: int* data; // 拷贝构造函数 MyClass(const MyClass other) { data new int(*other.data); std::cout 拷贝构造函数被调用 std::endl; } }; int main() { MyClass obj1; obj1.data new int(42); // 场景1通过已有对象初始化新对象 MyClass obj2 obj1; // 拷贝构造函数 // 场景2函数传参按值传递 void func(MyClass param); // 调用func(obj1)时会调用拷贝构造 // 场景3函数返回值某些情况下 MyClass createObj() { MyClass local; return local; // 可能调用拷贝构造取决于编译器优化 } // 场景4初始化列表 MyClass obj3(obj1); // 拷贝构造函数 return 0; }4.2 拷贝赋值运算符的调用场景class MyClass { public: int* data; // 拷贝赋值运算符 MyClass operator(const MyClass other) { if (this ! other) { // 自赋值检查 delete data; // 释放旧资源 data new int(*other.data); // 分配新资源 std::cout 拷贝赋值运算符被调用 std::endl; } return *this; } }; int main() { MyClass obj1, obj2, obj3; obj1.data new int(10); obj2.data new int(20); obj3.data new int(30); // 场景1直接赋值 obj2 obj1; // 拷贝赋值运算符 // 场景2链式赋值 obj3 obj2 obj1; // 连续调用拷贝赋值运算符 // 场景3赋值给已有对象 MyClass obj4; // ... 一些操作后 ... obj4 obj1; // 拷贝赋值运算符 return 0; }5. 实现要点与注意事项5.1 拷贝构造函数的实现要点深拷贝必要性如果类包含指针成员或需要独占的资源必须实现深拷贝初始化列表尽量使用成员初始化列表特别是对于const成员和引用成员异常安全在可能抛出异常的操作前完成资源分配默认行为如果不定义拷贝构造函数编译器会生成一个执行浅拷贝的默认版本5.2 拷贝赋值运算符的实现要点自赋值检查必须检查this ! other避免释放自身资源异常安全使用copy-and-swap惯用法可以提高异常安全性返回*this必须返回*this的引用以支持链式赋值释放旧资源在分配新资源前必须先释放对象原有的资源处理const成员const成员不能通过赋值修改需要特殊处理5.3 完整示例实现Rule of Threeclass ResourceHolder { private: int* resource; size_t size; public: // 构造函数 ResourceHolder(size_t s 0) : size(s) { resource (s 0) ? new int[s] : nullptr; } // 析构函数 ~ResourceHolder() { delete[] resource; } // 拷贝构造函数深拷贝 ResourceHolder(const ResourceHolder other) : size(other.size) { if (other.resource) { resource new int[other.size]; std::copy(other.resource, other.resource other.size, resource); } else { resource nullptr; } std::cout 拷贝构造函数 std::endl; } // 拷贝赋值运算符 ResourceHolder operator(const ResourceHolder other) { if (this ! other) { // 1. 自赋值检查 // 2. 分配新资源异常安全 int* newResource nullptr; if (other.resource) { newResource new int[other.size]; std::copy(other.resource, other.resource other.size, newResource); } // 3. 释放旧资源 delete[] resource; // 4. 接管新资源 resource newResource; size other.size; std::cout 拷贝赋值运算符 std::endl; } return *this; // 5. 返回*this } // 打印资源信息 void print() const { std::cout 资源大小: size std::endl; } }; int main() { ResourceHolder rh1(5); // 构造函数 ResourceHolder rh2 rh1; // 拷贝构造函数创建新对象 ResourceHolder rh3(3); rh3 rh1; // 拷贝赋值运算符已存在对象 return 0; }6. 常见误区与最佳实践6.1 常见误区混淆调用时机误以为obj2 obj1总是调用拷贝构造函数实际是拷贝赋值忘记自赋值检查在拷贝赋值运算符中未检查自赋值导致资源被意外释放浅拷贝问题对包含指针的类使用默认拷贝导致多个对象共享同一资源异常不安全在拷贝赋值中先释放旧资源再分配新资源如果分配失败则对象处于无效状态6.2 最佳实践Rule of Three如果一个类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个那么它很可能需要全部三个使用copy-and-swap通过创建临时副本并交换来提高异常安全性优先使用delete如果不希望对象被拷贝使用 delete明确禁止考虑移动语义C11后对于可移动的资源实现移动构造函数和移动赋值运算符可以提高性能测试自赋值总是测试obj obj的情况7. 总结拷贝构造函数和拷贝赋值运算符是C中对象拷贝的两种不同机制拷贝构造函数用于创建新对象时的初始化目标对象尚未存在拷贝赋值运算符用于已存在对象之间的赋值需要处理自赋值和旧资源释放理解这两者的区别对于编写正确的资源管理代码至关重要。在实际开发中应遵循Rule of Three原则根据需要实现深拷贝并使用copy-and-swap等技巧保证异常安全。对于现代C还应考虑Rule of Five将移动语义纳入设计考量。
返回列表