C++书写拷贝构造函数,重载赋值操作符和clone函数需要注意问题

本文探讨了C++中拷贝构造函数、重载赋值操作符及析构函数的重要性,尤其关注如何避免递归调用问题,并提供了一种推荐的实现方式。

对于C++类:显示地写出拷贝构造函数,重载赋值操作符和析构函数是良好的习惯,但在写构造函数时需要注意一些容易出现的错误,如下面的代码:

 

#include <iostream>
using namespace std;
 
class M{
public:
    M(){}
    M(const M &m){
       cout<<"copy construtor"<<endl;
       operator =(m);
    }
    M operator =(const M &m){   //问题出在此处
       cout<<"operator ="<<endl;
       return *this;
    }
};
 
int main() {
    M m1;
    M m2;
    m2=m1;
    return 0;
}


在下面三种情况下会调用拷贝构造函数:

(1)用一个已经实例化了的该类对象,去实例化该类的另外一个对象;

(2)用该类的对象传值的方式作为一个函数的参数;

(3)一个函数返回值为该类的一个对象。

特别地,对于语句 M m;  M mm=m; 属于(1)情况,即语句M mm=m;调用的是拷贝构造函数,而不是构造函数。

 

但在重载=操作符时,返回值不是引用类型将导致程序运行出现严重问题。即如果出现上面会调用拷贝构造函数的三种情况之一,或者使用=操作符时,拷贝构造函数和operator =将循环递归调用,导致程序出现死循环。原因是拷贝构造函数和operator =之间不断地重复调用。

解决办法:将operator =的返回类型改为引用类型M&,此时调用operator =时不会去调用拷贝构造函数。

 

还有,若要写clone时,若通过下面的方式:

    M clone(){

       cout<<"clone"<<endl;

       return *this;

    }

前提是拷贝构造函数不能调用clone来完成拷贝,否则出现上面同样的问题,下面的代码就会出现这样的问题

    M(const M &m){

       cout<<"copy construtor"<<endl;

       clone();

    }

总之,在写这些函数时,要特别留意彼此的调用关系。

以下是我的惯用写法:

(A)对于拷贝构造函数和重载=操作符

    M(const M &m){

       cout<<"copy construtor"<<endl;

       operator =(m);

    }

    M& operator =(const M &m){  //问题出在此处

       cout<<"operator ="<<endl;

       /* 此处写上成员数据的拷贝 */

       return *this;

    }

这里写成了inline函数,只是方便说明问题,其实不必非要这么写,可以采取先声明,后定义的常规方法。

(B)对于clone函数

声明:  virtual M clone();   //考虑继承时的多态

定义:  M M::clone(){

       cout<<"clone"<<endl;

       //将在调用处直接调用构造函数,效率高,避免返回局部变量,更安全

       return M();

    }

 

 

 

 

<think>我们有两个结构体指针p1p2,需要将p1指向的结构体的内容深拷贝给p2指向的结构体(假设p2已经指向一个结构体对象,或者我们需要先为p2分配内存)。 最简单的方式是:如果结构体内部没有指针成员,那么可以直接使用`*p2 = *p1`(因为这样是成员拷贝,对于基本类型是深拷贝);但如果结构体包含指针成员,我们就需要自定义深拷贝操作。 然而,问题要求最简单的方式,并且是针对结构体指针的深拷贝。这里有两种情况: 1. 如果结构体中不包含指针等需要深拷贝的成员(即所有成员都是基本类型或具有值语义的对象),那么直接解引用赋值即可。 2. 如果结构体包含指针,那么我们需要手动复制指针所指向的数据。 由于问题中要求深拷贝,并且要最简单的方式,我们可以考虑以下方法: 假设结构体类型为MyStruct,并且包含指针成员,那么我们可以为MyStruct实现一个深拷贝函数(例如clone)或者重载赋值运算符。但是,如果结构体定义不允许修改(比如是第三方库的),我们就需要在外部进行深拷贝。 下面给出一个外部深拷贝的示例(当无法修改结构体定义时): 步骤: a. 为p2分配内存(如果p2还没有指向有效的内存)。 b. 复制p1指向的结构体的非指针成员。 c. 对于指针成员,分配新的内存,然后复制数据。 但是,如果结构体定义已知,且允许修改,那么在结构体内实现深拷贝(拷贝构造函数赋值运算符)是最佳方式。这样,我们就可以简单地用`*p2 = *p1`来实现深拷贝(前提是已经重载赋值运算符)。 下面我将分别展示两种情况: 情况1:可以修改结构体定义(推荐做法) ```cpp #include <iostream> #include <cstring> struct MyStruct { int id; char* name; // 指针成员 // 构造函数 MyStruct(int i, const char* n) : id(i) { if (n) { name = new char[strlen(n) + 1]; strcpy(name, n); } else { name = nullptr; } } // 深拷贝赋值运算符 MyStruct& operator=(const MyStruct& other) { if (this != &other) { id = other.id; // 释放原有的name delete[] name; // 深拷贝name if (other.name) { name = new char[strlen(other.name) + 1]; strcpy(name, other.name); } else { name = nullptr; } } return *this; } // 析构函数 ~MyStruct() { delete[] name; } }; int main() { MyStruct* p1 = new MyStruct(1, "Hello"); MyStruct* p2 = new MyStruct(2, "World"); // 深拷贝:将p1指向的内容赋值给p2指向的对象 *p2 = *p1; // 由于重载赋值运算符,这里执行深拷贝 // 修改p1的name不会影响p2 p1->name[0] = 'h'; // 将Hello改为hello std::cout << "p1->name: " << p1->name << std::endl; // 输出: hello std::cout << "p2->name: " << p2->name << std::endl; // 输出: Hello (深拷贝,所以未改变) delete p1; delete p2; return 0; } ``` 情况2:无法修改结构体定义(外部实现深拷贝) ```cpp #include <iostream> #include <cstring> struct MyStruct { int id; char* name; }; // 深拷贝函数:将src指向的结构体深拷贝到dest指向的结构体 void deepCopy(MyStruct* dest, const MyStruct* src) { // 复制非指针成员 dest->id = src->id; // 释放dest原有的name(如果之前有分配) if (dest->name) { delete[] dest->name; dest->name = nullptr; } // 深拷贝src的name if (src->name) { dest->name = new char[strlen(src->name) + 1]; strcpy(dest->name, src->name); } else { dest->name = nullptr; } } int main() { MyStruct* p1 = new MyStruct{1, nullptr}; // 分配p1的name p1->name = new char[6]; strcpy(p1->name, "Hello"); MyStruct* p2 = new MyStruct{2, nullptr}; // 分配p2的name(假设之前有内容) p2->name = new char[6]; strcpy(p2->name, "World"); // 深拷贝:将p1的内容拷贝到p2 deepCopy(p2, p1); // 修改p1的name p1->name[0] = 'h'; std::cout << "p1->name: " << p1->name << std::endl; // 输出: hello std::cout << "p2->name: " << p2->name << std::endl; // 输出: Hello // 释放内存 delete[] p1->name; delete p1; delete[] p2->name; delete p2; return 0; } ``` 注意:在情况2中,我们假设结构体没有提供深拷贝支持,因此我们写了一个外部函数`deepCopy`来实现深拷贝。这种方法需要知道结构体的内部结构,并且需要小心内存管理。 总结:最简单的方式是修改结构体定义,实现深拷贝赋值运算符,然后就可以用`*p2 = *p1`来实现深拷贝。如果无法修改结构体,则需要手动编写深拷贝函数。 §§相关问题§§: 1. 在什么情况下,结构体的默认赋值操作符是安全的(不会导致内存问题)? 2. 如何为一个包含动态分配内存的结构体实现深拷贝构造函数? 3. 什么是拷贝赋值运算符?为什么要返回引用? 4. 在深拷贝函数中,为什么需要检查自赋值? </think>### 结构体指针深拷贝的最简实现 在 C++ 中实现结构体指针的深拷贝,最简单的方式是使用**值拷贝结合动态内存分配**。以下是两种最简洁的实现方法: #### 方法1:直接值拷贝(推荐) ```cpp #include <iostream> #include <cstring> // 用于 memcpy struct Employee { int id; char* name; // 指针成员 }; int main() { // 创建源结构体 Employee* p1 = new Employee; p1->id = 100; p1->name = new char[20]; strcpy(p1->name, "Alice"); // 创建目标结构体 Employee* p2 = new Employee; // 深拷贝核心操作(两步完成) *p2 = *p1; // 1. 拷贝非指针成员 p2->name = new char[strlen(p1->name) + 1]; // 2. 为指针成员分配新内存 strcpy(p2->name, p1->name); // 复制内容 // 修改源数据验证独立性 p1->id = 200; strcpy(p1->name, "Bob"); // 验证深拷贝结果 std::cout << "p2->id: " << p2->id << std::endl; // 输出 100 std::cout << "p2->name: " << p2->name << std::endl;// 输出 "Alice" // 释放内存 delete[] p1->name; delete p1; delete[] p2->name; delete p2; return 0; } ``` #### 方法2:封装拷贝函数(更安全) ```cpp #include <iostream> #include <cstring> struct Product { char* name; double price; }; // 深拷贝函数 void deepCopy(Product* dest, const Product* src) { // 拷贝基本类型成员 dest->price = src->price; // 深拷贝指针成员 if (src->name) { size_t len = strlen(src->name) + 1; dest->name = new char[len]; strcpy(dest->name, src->name); } else { dest->name = nullptr; } } int main() { Product* p1 = new Product; p1->name = new char[20]; strcpy(p1->name, "Laptop"); p1->price = 999.99; Product* p2 = new Product; // 一行调用完成深拷贝 deepCopy(p2, p1); // 验证 strcpy(p1->name, "Tablet"); std::cout << "p2->name: " << p2->name << std::endl; // 输出 "Laptop" delete[] p1->name; delete p1; delete[] p2->name; delete p2; return 0; } ``` ### 核心原理与优势 1. **分离拷贝操作** - 非指针成员:通过 `*p2 = *p1` 直接值拷贝 - 指针成员:单独分配新内存并复制内容 2. **内存独立性** ```mermaid graph LR p1-->id1[100] p1-->name1[Alice] p2-->id2[100] p2-->name2[Alice] ``` 每个指针成员指向独立的内存块 3. **简洁性优势** - 无需修改原始结构体定义 - 避免复杂的拷贝构造函数/赋值运算符重载 - 代码直观易于维护 ### 适用场景 此方法最适用于: 1. 无法修改结构体定义的场景 2. 临时性深拷贝需求 3. 简单结构体(成员较少时) 4. 原型开发或快速实现阶段
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值