文章

类的基本函数

类的基本函数

六大基本函数

默认构造函数 — T() 析构函数 — ~T() 拷贝构造函数 — T(const T&) 拷贝赋值运算符 — T& operator=(const T&) 移动构造函数 (C++11) — T(T&&) 移动赋值运算符 (C++11) — T& operator=(T&&)

在一个程序中演示

#include <iostream>
#include <algorithm>
#include <utility>

class Buffer {
private:
    std::size_t size_;
    int* data_;

public:
    // 1) 默认构造函数
    Buffer() : size_(0), data_(nullptr) {
        std::cout << "Default ctor\n";
    }

    // 额外构造:方便创建有大小的对象
    explicit Buffer(std::size_t n) : size_(n), data_(n ? new int[n] : nullptr) {
        std::cout << "Sized ctor\n";
        for (std::size_t i = 0; i < size_; ++i) data_[i] = 0;
    }

    // 2) 析构函数
    ~Buffer() {
        std::cout << "Dtor\n";
        delete[] data_;
    }

    // 3) 拷贝构造函数(深拷贝)
    Buffer(const Buffer& other) : size_(other.size_), data_(other.size_ ? new int[other.size_] : nullptr) {
        std::cout << "Copy ctor\n";
        std::copy(other.data_, other.data_ + size_, data_);
    }

    // 4) 拷贝赋值运算符(深拷贝 + 自赋值保护)
    Buffer& operator=(const Buffer& other) {
        std::cout << "Copy assignment\n";
        if (this == &other) return *this;

        int* new_data = other.size_ ? new int[other.size_] : nullptr;
        std::copy(other.data_, other.data_ + other.size_, new_data);

        delete[] data_;
        data_ = new_data;
        size_ = other.size_;
        return *this;
    }

    // 5) 移动构造函数(窃取资源)
    Buffer(Buffer&& other) noexcept : size_(other.size_), data_(other.data_) {
        std::cout << "Move ctor\n";
        other.size_ = 0;
        other.data_ = nullptr;
    }

    // 6) 移动赋值运算符(释放旧资源 + 窃取新资源)
    Buffer& operator=(Buffer&& other) noexcept {
        std::cout << "Move assignment\n";
        if (this == &other) return *this;

        delete[] data_;
        size_ = other.size_;
        data_ = other.data_;

        other.size_ = 0;
        other.data_ = nullptr;
        return *this;
    }

    int& operator[](std::size_t i) { return data_[i]; }
    const int& operator[](std::size_t i) const { return data_[i]; }
    std::size_t size() const { return size_; }
};

int main() {
    Buffer a(3);
    a[0] = 10; a[1] = 20; a[2] = 30;

    Buffer b = a;             // 触发拷贝构造
    Buffer c;   
    c = a;                    // 触发拷贝赋值

    Buffer d = std::move(a);  // 触发移动构造
    Buffer e;
    e = std::move(b);         // 触发移动赋值

    std::cout << "d size = " << d.size() << ", e size = " << e.size() << "\n";
    return 0;
}

用「杯子倒水」的小比喻,把拷贝构造 / 拷贝赋值 / 移动赋值三个一起讲清楚。

场景设定

  • 杯子 = 对象本身(那块内存/那个变量)
  • = 对象里的资源/数据(字符串内容、堆上的数组、文件句柄等)
  • 倒水 = 让一个杯子里的水变成和另一个一样
操作代码味道杯子比喻b 怎样
拷贝构造A a = b;新杯子,舀一份水还满着
拷贝赋值a = b;旧杯子换水,舀一份还满着
移动构造A a = std::move(b);新杯子,端走水变空/可销毁
移动赋值a = std::move(b);旧杯子换水,端走水变空/可销毁

关于赋值运算符

  • 拷贝构造 : 照着别人 新建 一个自己
  • 拷贝赋值 : 照着别人 改掉 已经有的自己
A a;      // 先有一个 a
A b;      // 又有一个 b
a = b;    // 这里触发的就是拷贝赋值:把 b 的内容抄一份,写进已经存在的 a 里

为何叫赋值而不是拷贝,

a = b;            // 拷贝赋值:b 还在,a 变成 b 的副本
a = std::move(b); // 移动赋值:尽量“偷走” b 的资源,b 被掏空

在 C++ 里,=+-[]<< 这些符号本身就叫 运算符(operator)

普通函数长这样:

void foo(int x);

=不是字母名字,没法写成=(a,b),所以C++规定:

用operator+赋值符号,组成函数名。

A& operator=(const A& other); // 函数名就是 operator=

读法:

“定义当别人对你的类型写 = 时,应该调用哪个函数”

所以:

你看到的实际含义
a = b;语法糖
a.operator=(b);真正调用的函数(也能手写)

两者通常等价。

// 更多
bool operator==(const A&, const A&);  // 定义 == 怎么比
A  operator+(const A&, const A&);     // 定义 + 怎么加
T& operator[](size_t);                // 定义 [] 怎么取下标

深拷贝与浅拷贝

浅拷贝只复制指针,共享底层资源;深拷贝复制资源本身,对象彼此独立。

有堆内存所有权时,浅拷贝常导致数据互相干扰或 double-free。

 浅拷贝深拷贝
做什么只复制指针/句柄本身新开一份资源,内容也复制过去
结果两个对象共用同一块资源两个对象各自一份资源
风险一个改了另一个也变;析构时可能双重释放更安全,但更慢、更费内存

浅拷贝例子与风险

#include <cstddef>
#include <iostream>

class BadBuffer {
private:
    std::size_t size_;
    int* data_;

public:
    explicit BadBuffer(std::size_t n = 0)
        : size_(n), data_(n ? new int[n] : nullptr) {
        std::cout << "Ctor\n";
    }

    ~BadBuffer() {
        std::cout << "Dtor, delete[] data_\n";
        delete[] data_;
    }

    // 错误1:浅拷贝(两个对象指向同一块堆内存)
    BadBuffer(const BadBuffer& other)
        : size_(other.size_), data_(other.data_) {
        std::cout << "Bad copy ctor (shallow copy)\n";
    }

    // 错误2:浅拷贝赋值(同样共享指针)
    BadBuffer& operator=(const BadBuffer& other) {
        if (this == &other) return *this;
        delete[] data_;               // 先释放自己原来的
        size_ = other.size_;
        data_ = other.data_;          // 直接复制指针(错误)
        std::cout << "Bad copy assignment (shallow copy)\n";
        return *this;
    }

    int& operator[](std::size_t i) { return data_[i]; }
};

int main() {
    BadBuffer a(3);
    a[0] = 42;

    {
        BadBuffer b = a;   // 浅拷贝:a.data_ 和 b.data_ 指向同一块内存
    } // b 析构,第一次 delete[]

    // 这里 a.data_ 已经悬空,再用就是未定义行为
    // std::cout << a[0] << "\n";

    // main 结束时 a 析构,第二次 delete[] 同一地址 -> double free
    return 0;
}

深拷贝例子

#include <cstddef>
#include <iostream>

class GoodBuffer {
private:
    std::size_t size_;
    int* data_;

public:
    // 普通构造
    explicit GoodBuffer(std::size_t n = 0)
        : size_(n), data_(n ? new int[n] : nullptr) {
        for (std::size_t i = 0; i < size_; ++i) {
            data_[i] = 0;
        }
    }

    // 1) 析构函数
    ~GoodBuffer() {
        delete[] data_;
    }

    // 2) 拷贝构造:深拷贝
    GoodBuffer(const GoodBuffer& other)
        : size_(other.size_), data_(other.size_ ? new int[other.size_] : nullptr) {
        for (std::size_t i = 0; i < size_; ++i) {
            data_[i] = other.data_[i];
        }
    }

    // 3) 拷贝赋值:深拷贝 + 自赋值保护
    GoodBuffer& operator=(const GoodBuffer& other) {
        if (this == &other) return *this;

        int* newData = other.size_ ? new int[other.size_] : nullptr;
        for (std::size_t i = 0; i < other.size_; ++i) {
            newData[i] = other.data_[i];
        }

        delete[] data_;
        data_ = newData;
        size_ = other.size_;
        return *this;
    }

    // 4) 移动构造:窃取资源
    GoodBuffer(GoodBuffer&& other) noexcept
        : size_(other.size_), data_(other.data_) {
        other.size_ = 0;
        other.data_ = nullptr;
    }

    // 5) 移动赋值:释放旧资源 + 窃取新资源
    GoodBuffer& operator=(GoodBuffer&& other) noexcept {
        if (this == &other) return *this;

        delete[] data_;
        data_ = other.data_;
        size_ = other.size_;

        other.data_ = nullptr;
        other.size_ = 0;
        return *this;
    }

    int& operator[](std::size_t i) { return data_[i]; }
    const int& operator[](std::size_t i) const { return data_[i]; }
    std::size_t size() const { return size_; }
};

int main() {
    GoodBuffer a(3);
    a[0] = 42;
    a[1] = 7;
    a[2] = 99;

    GoodBuffer b = a;            // 拷贝构造(深拷贝)
    GoodBuffer c;
    c = a;                       // 拷贝赋值(深拷贝)

    GoodBuffer d = std::move(a); // 移动构造
    GoodBuffer e;
    e = std::move(b);            // 移动赋值

    std::cout << d[0] << " " << e[1] << " size=" << e.size() << "\n";
    return 0;
}

三大法则 / 五大法则 / 零法则

Rule of Zero(推荐默认态度):

优先使用智能指针和标准容器管理资源,让编译器自动生成所有特殊成员函数,无需手动定义任何一个。

  • 成员用 string / vector / unique_ptr 等自动管理资源
  • 类本身不 new/delete、不开文件、不握裸句柄 → 六个都可以不写,让编译器生成,通常正确且高效。
#include <iostream>
#include <vector>
#include <utility>

class BufferZero {
private:
    std::vector<int> data_;  // 由 vector 自动管理内存

public:
    explicit BufferZero(std::size_t n = 0) : data_(n, 0) {
        std::cout << "Ctor\n";
    }

    // 不写析构、拷贝构造、拷贝赋值、移动构造、移动赋值
    // 编译器生成的默认行为通常就是我们想要的

    int& operator[](std::size_t i) { return data_[i]; }
    const int& operator[](std::size_t i) const { return data_[i]; }
    std::size_t size() const { return data_.size(); }
};

int main() {
    BufferZero a(3);
    a[0] = 10; a[1] = 20; a[2] = 30;

    BufferZero b = a;              // 自动深拷贝(vector 语义)
    BufferZero c;
    c = a;                         // 自动拷贝赋值

    BufferZero d = std::move(a);   // 自动移动构造
    BufferZero e;
    e = std::move(b);              // 自动移动赋值

    std::cout << "d size = " << d.size() << ", e size = " << e.size() << "\n";
    return 0;
}

Rule of Three(C++98 思维,现在仍重要): 若需要自定义析构 / 拷贝构造 / 拷贝赋值之一,另外两个往往也要管。

Rule of Five(C++11+): 再加上移动构造、移动赋值,五个(加默认构造看需求)一起考虑。

实用口诀

  1. 能不手写就不手写(成员用标准库资源类型)
  2. 要手写就成套写,或显式 = default / = delete 表态
  3. 有裸指针所有权 → 别假装“不写也没事”
1
2
3
4
5
6
7
8
9
// 现代写法示例:禁止拷贝,保留移动
struct File {
    File() = default;
    File(const File&) = delete;
    File& operator=(const File&) = delete;
    File(File&&) noexcept = default;
    File& operator=(File&&) noexcept = default;
    ~File() = default;
};

扩展阅读

[1] https://zh.wikipedia.org/wiki/%E6%9E%84%E9%80%A0%E5%99%A8

本文由作者按照 CC BY 4.0 进行授权