复制构造函数和operator=的一点注意事项

C++拷贝构造与赋值操作
本文探讨了C++中拷贝构造函数及赋值操作符的使用细节,特别是如何在派生类中正确地调用基类的相关构造函数和赋值操作符。
 
 
class GameBoard
{
public:
    
    GameBoard()
    {
       std::cout << "GameBoard()" << std::endl;
    }

    GameBoard(const GameBoard&)
    {
       std::cout << "GameBoard(const GameBoard&)" << std::endl;
    }

    GameBoard& operator=(const GameBoard& other)
    {
       std::cout<< "GameBoard& operator=()" << std::endl;
       return *this;
    }

    ~GameBoard()
    {
       std::cout << "~GameBoard" << std::endl;
    }
};

class Game
{
public:
    Game()
    {
      std::cout << "Game()" << std::endl;
    }

    ~Game()
    {
      std::cout << "~Game()" << std::endl;
    }
    //显示调用子对象复制构造函数
    Game(const Game& other):gb(other.gb)
    {
       std::cout << "Game(const Game& other)" << std::endl;
    }

    Game& operator=(const Game& other)
    {
       gb = other.gb; //显示调用子对象赋值函数
       std::cout << "Game& operator=()" << std::endl;
       return *this;
    }

private:
    GameBoard gb;
};

class Checkers : public Game
{
public:

    Checkers()
    {
       std::cout << "Checkers()" << std::endl;
    }

    ~Checkers()
    {
       std::cout << "~Checkers()" << std::endl;
    }

    Checkers(const Checkers& other):Game(other) //显示调用基类复制构造函数
    {
       std::cout << "Checkers(const Checkers& other)" << std::endl;
    }

    Checkers& operator=(const Checkers& other)
    {
        Game::operator=(other); //显示调用基类赋值函数
       std::cout << "Checkers& operator=()" << std::endl;
    }
};
 

总结:

1.复制构造函数默认调用基类的默认构造函数,不会自动调用基类复制构造函数。当派生类写拷贝构造函数时,要显示调用基类的拷贝构造函数,如下:

Derived(const Derived& d):Base(d)

{

}

2.operator= 不会自动调用基类的operator=,当为派生类写赋值操作符,要显示调用基类版本,如下:

Derived& operator=(const Derived& d)

{

    Base::operator=(d);

}

 

 


 

<think>在C++中,拷贝构造函数赋值运算符(operator=)都是用于对象的复制,但它们的调用时机用途有本质区别。下面详细解释它们的关系区别。 ### 1. 拷贝构造函数(Copy Constructor) - **定义**:用同类型的另一个对象初始化新对象。 - **调用时机**: - 对象初始化时(如 `MyClass a = b;` 或 `MyClass a(b);`) - 函数参数按值传递对象时 - 函数返回对象时(可能被编译器优化) - **声明形式**: ```cpp MyClass(const MyClass& other); ``` ### 2. 赋值运算符(operator=) - **定义**:将已存在的对象赋值给另一个已存在的对象。 - **调用时机**: - 两个对象都已存在时进行赋值(如 `a = b;`) - **声明形式**: ```cpp MyClass& operator=(const MyClass& other); ``` ### 3. 关系与区别 | 特性 | 拷贝构造函数 | 赋值运算符 | |--------------|--------------------------|---------------------------| | **目的** | 初始化新对象 | 覆盖已存在对象的值 | | **调用时机** | 对象创建时 | 对象已存在时 | | **返回值** | 无(构造函数) | 返回当前对象的引用 | | **参数** | 常量引用 | 常量引用 | | **深拷贝** | 必须实现(避免浅拷贝问题)| 必须实现(避免内存泄漏) | ### 4. 代码示例 ```cpp class String { private: char* data; size_t length; public: // 构造函数 String(const char* str = "") { length = strlen(str); data = new char[length + 1]; strcpy(data, str); } // 析构函数 ~String() { delete[] data; } // 拷贝构造函数 String(const String& other) { length = other.length; data = new char[length + 1]; strcpy(data, other.data); } // 赋值运算符 String& operator=(const String& other) { if (this != &other) { // 防止自赋值 delete[] data; // 释放原有资源 length = other.length; data = new char[length + 1]; strcpy(data, other.data); } return *this; // 返回当前对象的引用 } }; ``` ### 5. 关键注意事项 - **自赋值检查**:赋值运算符中必须检查 `if(this != &other)` - **异常安全**:赋值运算符应保证在分配新内存失败时对象仍有效(可用拷贝交换技巧) ```cpp String& operator=(String other) { // 注意:按值传递(调用拷贝构造函数) swap(other); // 交换当前对象临时对象的内容 return *this; // 临时对象析构时释放原资源 } void swap(String& other) noexcept { std::swap(data, other.data); std::swap(length, other.length); } ``` - **Rule of Three**:如果定义了拷贝构造函数、赋值运算符或析构函数中的任意一个,通常需要同时定义这三个。 ### 6. 常见错误 - **浅拷贝问题**:未实现深拷贝导致多个对象共享同一内存(双重释放) - **内存泄漏**:赋值运算符中未释放原有内存 - **自赋值问题**:未检查自赋值导致资源被提前释放 §§相关问题§§: 1. 什么是C++中的Rule of Three? 2. 如何实现一个异常安全的赋值运算符? 3. 拷贝构造函数赋值运算符在什么情况下会被编译器自动生成? 4. 为什么拷贝构造函数的参数必须是引用类型?</think>### 赋值运算符重载(operator=)与拷贝构造函数的关系 在C++中,赋值运算符重载(`operator=`)拷贝构造函数都是用于对象复制的特殊成员函数,但它们有不同的使用场景行为。以下是它们的关系详解: #### 1. **核心区别** | 特性 | 拷贝构造函数 | 赋值运算符重载 | |------|-------------|---------------| | **调用时机** | 创建新对象时 | 已存在对象赋值时 | | **用途** | 初始化新对象 | 修改已有对象 | | **语法** | `ClassName(const ClassName&)` | `ClassName& operator=(const ClassName&)` | | **返回值** | 无 | 返回当前对象的引用 | | **示例** | `Person p2 = p1;` | `p2 = p1;` | #### 2. **代码示例** ```cpp #include <iostream> #include <cstring> class Person { private: char* name; int age; public: // 构造函数 Person(const char* n, int a) : age(a) { name = new char[strlen(n) + 1]; strcpy(name, n); } // 拷贝构造函数 Person(const Person& other) : age(other.age) { name = new char[strlen(other.name) + 1]; strcpy(name, other.name); std::cout << "拷贝构造调用\n"; } // 赋值运算符重载 Person& operator=(const Person& other) { if (this != &other) { // 1. 检查自赋值 delete[] name; // 2. 释放现有资源 age = other.age; // 3. 复制基本类型 name = new char[strlen(other.name) + 1]; // 4. 分配新资源 strcpy(name, other.name); } std::cout << "赋值运算符调用\n"; return *this; // 5. 返回当前对象 } ~Person() { delete[] name; } void print() const { std::cout << name << " (" << age << ")\n"; } }; int main() { Person alice("Alice", 30); Person bob = alice; // 调用拷贝构造函数 Person charlie("Charlie", 25); bob = charlie; // 调用赋值运算符 alice.print(); // Alice (30) bob.print(); // Charlie (25) } ``` #### 3. **关键关系** 1. **互补作用**: - 拷贝构造函数:用于从无到有创建新对象 - 赋值运算符:用于修改已存在对象的状态 2. **实现相似性**: - 两者都需要处理深拷贝(特别是动态内存) - 都需要防止浅拷贝问题 3. **Rule of Three**: ```cpp // 三者通常需要同时定义 class RuleOfThree { public: ~RuleOfThree(); // 析构函数 RuleOfThree(const RuleOfThree&); // 拷贝构造 RuleOfThree& operator=(const RuleOfThree&); // 赋值运算符 }; ``` 如果定义了其中任意一个,通常需要同时定义另外两个。 #### 4. **特殊注意事项** 1. **自赋值处理**: ```cpp // 赋值运算符必须处理自赋值 Person& operator=(const Person& other) { if (this == &other) return *this; // 关键检查 // ...其他操作... } ``` 2. **异常安全**: ```cpp // 更好的实现(强异常安全) Person& operator=(const Person& other) { char* newName = new char[strlen(other.name) + 1]; strcpy(newName, other.name); delete[] name; // 后释放旧资源 name = newName; age = other.age; return *this; } ``` 3. **现代C++改进**: ```cpp // 使用拷贝交换惯用法 (Copy-and-Swap) Person& operator=(Person other) { // 按值传递(调用拷贝构造) swap(*this, other); // 交换内容 return *this; // 返回时自动销毁旧资源 } friend void swap(Person& a, Person& b) { std::swap(a.name, b.name); std::swap(a.age, b.age); } ``` #### 5. **常见错误** 1. **未处理自赋值**: ```cpp // 错误示例:自赋值导致资源提前释放 Person& operator=(const Person& other) { delete[] name; // 如果 this == &other,name 已被删除 // ... } ``` 2. **资源泄漏**: ```cpp // 错误示例:未释放旧资源 Person& operator=(const Person& other) { name = new char[strlen(other.name) + 1]; // 旧内存泄漏 // ... } ``` 3. **浅拷贝问题**: ```cpp // 错误示例:默认生成的拷贝操作是浅拷贝 Person p1("Alice"); Person p2 = p1; // p2.name p1.name 指向同一内存 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值