std::unique_ptr成员函数用法

首先需要注意,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

相关推荐
Tairitsu_H2 小时前
[C++] 拷贝构造还是赋值重载?类默认成员函数细节详解
开发语言·c++·类和对象
汉克老师3 小时前
GESP2026年9月认证C++四级( 第一部分选择题(1~7题)精讲
c++·gesp·小学生·学c++编程
geats人山人海3 小时前
现代c++第二章 字符串
c++
idealzouhu3 小时前
现代 CMake 依赖管理详解:从构建树、Target 封装到 find_package 工作原理
c++·cmake
四代水门4 小时前
游戏客户端——算法题
c++
Xia__Quan4 小时前
centos 共享文件夹
c++
hi_ro_a5 小时前
基于正倒排索引的boost搜索引擎
linux·c++·搜索引擎·项目
神仙别闹5 小时前
基于 C++ MFC 实现的个人通讯录管理系统
c++
GG-_-Bond5 小时前
9.1kv存储持久化模拟面试
linux·c语言·数据结构·c++