首先需要注意,std::unique_ptr 本身显式禁用了拷贝构造函数和拷贝赋值运算符。
std::unique_ptr 实现了独占所有权语义(Exclusive Ownership),确保同一时间内只有一个 unique_ptr 可以拥有并管理指定的对象资源。
源码实现逻辑
在 C++ 标准库中,std::unique_ptr 通过 = delete 显式禁用了拷贝操作:
cpp
template <typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
public:
// 禁用了拷贝构造函数
unique_ptr(const unique_ptr&) = delete;
// 禁用了拷贝赋值运算符
unique_ptr& operator=(const unique_ptr&) = delete;
// 允许移动构造函数和移动赋值运算符
unique_ptr(unique_ptr&&) noexcept;
unique_ptr& operator=(unique_ptr&&) noexcept;
};
行为表现与代码示例
1. 尝试拷贝(编译错误)
试图直接复制 std::unique_ptr 会在编译期直接报错:
cpp
#include <memory>
int main() {
auto p1 = std::make_unique<int>(42);
// auto p2 = p1; // 编译错误!拷贝构造函数已被禁用 (= delete)
// auto p2;
// p2 = p1; // 编译错误!拷贝赋值运算符已被禁用 (= delete)
}
2. 所有权转移(移动语义)
虽然不能拷贝,但可以通过 std::move 将对象的所有权转让给另一个 unique_ptr。转移后,原指针会变为空指针(nullptr):
cpp
#include <memory>
#include <iostream>
int main() {
auto p1 = std::make_unique<int>(42);
// 移交所有权
std::unique_ptr<int> p2 = std::move(p1);
// 验证状态
if (p1 == nullptr) {
std::cout << "p1 已经变为空指针\n";
}
std::cout << "*p2: " << *p2 << "\n"; // 输出 42
}
3. 对自定义类(Class/Struct)的影响
如果一个自定义类将 std::unique_ptr 作为成员变量,由于 unique_ptr 无法拷贝,编译器会自动禁用该自定义类的默认拷贝构造函数和拷贝赋值运算符。
cpp
struct Widget {
std::unique_ptr<int> data;
};
int main() {
Widget w1;
// Widget w2 = w1; // 编译错误!Widget 的默认拷贝操作已被自动隐式禁用
Widget w2 = std::move(w1); // 正确!Widget 仍然支持移动
}
只要你的类中包含了 std::unique_ptr 类型的成员,编译器在发现你尝试拷贝该类的对象时,就会直接在编译期报错。
如果你的业务场景确实需要这个类支持"拷贝",可以通过深拷贝(Deep Copy)手动实现拷贝构造函数。
默认行为:编译器自动禁用拷贝
根据 C++ 标准规定,如果一个类的某个成员变量不可拷贝(例如 std::unique_ptr 的拷贝构造函数是 = delete),编译器将隐式禁用该类的默认拷贝构造函数和默认拷贝赋值运算符。
cpp
#include <memory>
class MyClass {
std::unique_ptr<int> data_;
public:
MyClass(int val) : data_(std::make_unique<int>(val)) {}
// 注意:这里我们没有写任何 = delete 或拷贝构造函数
};
int main() {
MyClass obj1(42);
// 编译报错!编译器无法为你生成默认的拷贝构造函数
// MyClass obj2 = obj1;
// 正确!移动语义依然有效(编译器会自动生成默认的移动构造函数)
MyClass obj3 = std::move(obj1);
}
如果你"不需要"这个类被拷贝(最常见)
你不需要 在类里手动写 MyClass(const MyClass&) = delete;。
不过,根据 C++ 5法则(Rule of 5),如果你想让代码意图更清晰,可以显式声明(但这仅仅是为了提供更友好的代码可读性,对编译结果没有本质影响):
cpp
class MyClass {
std::unique_ptr<int> data_;
public:
MyClass(int val) : data_(std::make_unique<int>(val)) {}
// 【可选】显式写出以提高代码可读性
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
// 如果显式写了 delete,建议显式声明移动操作
MyClass(MyClass&&) noexcept = default;
MyClass& operator=(MyClass&&) noexcept = default;
};
如果你"需要"这个类支持拷贝
如果你希望你的类可以被拷贝(例如拷贝时复制一份全新的底层资源),你可以手动编写拷贝构造函数实现深拷贝:
cpp
#include <memory>
class MyClass {
std::unique_ptr<int> data_;
public:
MyClass(int val) : data_(std::make_unique<int>(val)) {}
// 手动实现【深拷贝构造函数】
MyClass(const MyClass& other) {
if (other.data_) {
// 为当前对象重新分配内存,并复制值
data_ = std::make_unique<int>(*other.data_);
}
}
// 手动实现【深拷贝赋值运算符】
MyClass& operator=(const MyClass& other) {
if (this != &other) {
if (other.data_) {
data_ = std::make_unique<int>(*other.data_);
} else {
data_.reset();
}
}
return *this;
}
// 显式保留默认移动操作
MyClass(MyClass&&) noexcept = default;
MyClass& operator=(MyClass&&) noexcept = default;
};
int main() {
MyClass obj1(42);
MyClass obj2 = obj1; // 正常工作!obj2 拥有自己的 unique_ptr 和独立的内存区域
}
验证步骤
要验证某个类型是否可拷贝,可以使用静态断言:
cpp
#include <memory>
#include <type_traits>
// 验证不可拷贝
static_assert(!std::is_copy_constructible_v<std::unique_ptr<int>>, "unique_ptr 应禁用拷贝构造");
static_assert(!std::is_copy_assignable_v<std::unique_ptr<int>>, "unique_ptr 应禁用拷贝赋值");
// 验证支持移动
static_assert(std::is_move_constructible_v<std::unique_ptr<int>>, "unique_ptr 应支持移动构造");
static_assert(std::is_move_assignable_v<std::unique_ptr<int>>, "unique_ptr 应支持移动赋值");
end