类的基本函数
类的基本函数
六大基本函数
默认构造函数 — 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+): 再加上移动构造、移动赋值,五个(加默认构造看需求)一起考虑。
实用口诀
- 能不手写就不手写(成员用标准库资源类型)
- 要手写就成套写,或显式
= default/= delete表态 - 有裸指针所有权 → 别假装“不写也没事”
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 进行授权