在 C++ 中,不能复制(Non-Copyable)但可以移动(Movable) 的类型通常是那些用于管理独占性资源的类。
这些类的设计目标是确保在任何给定时间,只有一个对象拥有该资源的所有权,从而防止资源被重复释放或发生混乱。
核心类型:独占性资源管理器
以下是 C++ 标准库中一些最重要、最常见的只能移动不能复制的类型:
1. 内存和资源所有权管理
| 类型 | 托管的资源 | 为什么不能复制? |
|---|---|---|
std::unique_ptr |
独占性地管理堆上的动态内存。 | 如果复制,两个指针将指向同一块内存。当其中一个析构时,内存会被释放,另一个指针将变成悬空指针(Dangling Pointer)。 |
std::fstream / std::ifstream / std::ofstream |
文件句柄(File Handle)。 | 复制会导致两个对象试图管理同一个操作系统文件描述符,可能导致关闭两次或访问冲突。 |
std::thread |
操作系统线程的执行上下文。 | 复制一个线程对象意味着创建两个对象来控制同一个系统线程的生命周期和同步点,这是逻辑上不允许的。 |
2. 并发和异步工具
| 类型 | 托管的资源 | 为什么不能复制? |
|---|---|---|
std::mutex |
互斥锁的底层系统资源。 | 如果复制,两个 mutex 对象将代表同一个锁,使得锁机制失效,无法保证互斥。 |
std::lock_guard / std::unique_lock |
对 std::mutex 的独占性锁定状态。 |
它们是 RAII 锁,复制会导致两个对象试图同时管理同一个锁,破坏了独占性。 |
std::promise |
异步操作结果的共享状态。 | 复制将导致两个 promise 实例可以对同一个共享状态设置结果,这会破坏"单次设置"的规则。 |
std::packaged_task |
异步函数和其内部的 std::promise。 |
它封装了 std::promise,因此也必须保证独占所有权,确保任务只被执行一次。 |
std::future |
异步操作的获取句柄。 | 虽然有些 future 实现可以复制(如 std::shared_future),但默认的 std::future 通常只能移动,因为它代表了对结果的独占性访问。 |
3. C++20 同步原语
-
std::jthread:C++20 中的 RAII 线程,它自动join,继承了std::thread不可复制的特性。 -
std::counting_semaphore/std::binary_semaphore:信号量的底层资源是独占的。
总结和设计原则
当你设计一个 C++ 类时,如果这个类管理着一个无法安全共享的底层资源(如操作系统句柄、网络套接字、文件描述符、内存块的所有权),那么你应该:
-
禁用拷贝构造函数和拷贝赋值运算符 (C++11/14/17 通常使用
= delete)。 -
实现移动构造函数和移动赋值运算符(将底层资源指针/句柄从源对象转移到目标对象,并清空源对象)。
通过这种设计,C++ 编译器强制执行了资源所有权的转移,保证了资源的生命周期安全,这是现代 C++ 编程中一个重要的设计范式。