unique_ptr 是不共享的智能指针。其不能被拷贝,不能通过传值的方式作为参数传递给函数,也不能用于 STL 中需要 copy 的算法中。

unique_ptr 能被 move。当 unique_ptr 被 move 时,unique_ptr 中内存资源的属主会被转移。

比较推荐的做法是一个对象有一个属主,因为一个对象有多个属主时会增加程序逻辑的复杂性,例如多线程时会有竞争。

右边的图例举了一个属主转移的例子;刚开始对象的属主是 ptrA,当执行 move 后,对象属主变为了 ptrB。

unique_ptr0

unique_ptr 定义在 <memory> 头文件中。其效率和原始指针差不多,也支持 * 运算符。

构造 unique_ptr 时,可以使用帮助函数 make_unique 。例如:

auto ptrA = make_unique<int>(10);

Examples

  • 如何创建 unique_ptr 实例

    unique_ptr<Song> SongFactory(const std::wstring& artist, const std::wstring& title)
    {
    // Implicit move operation into the variable that stores the result.
    return make_unique<Song>(artist, title);
    }

    void MakeSongs()
    {
    // Create a new unique_ptr with a new object.
    auto song = make_unique<Song>(L"Mr. Children", L"Namonaki Uta");

    // Use the unique_ptr.
    vector<wstring> titles = { song->title };

    // Move raw pointer from one unique_ptr to another.
    unique_ptr<Song> song2 = std::move(song);

    // Obtain unique_ptr from function that returns by value.
    auto song3 = SongFactory(L"Michael Jackson", L"Beat It");
    }
  • 如何在容器中使用 unique_ptr

    void SongVector()
    {
    vector<unique_ptr<Song>> songs;

    // Create a few new unique_ptr<Song> instances
    // and add them to vector using implicit move semantics.
    songs.push_back(make_unique<Song>(L"B'z", L"Juice"));
    songs.push_back(make_unique<Song>(L"Namie Amuro", L"Funky Town"));
    songs.push_back(make_unique<Song>(L"Kome Kome Club", L"Kimi ga Iru Dake de"));
    songs.push_back(make_unique<Song>(L"Ayumi Hamasaki", L"Poker Face"));

    // Pass by const reference when possible to avoid copying.
    for (const auto& song : songs)
    {
    wcout << L"Artist: " << song->artist << L" Title: " << song->title << endl;
    }
    }
  • 将 unique_ptr 作为类成员如何使用

    class MyClass
    {
    private:
    // MyClass owns the unique_ptr.
    unique_ptr<ClassFactory> factory;
    public:

    // Initialize by using make_unique with ClassFactory default constructor.
    MyClass() : factory (make_unique<ClassFactory>())
    {
    }

    void MakeClass()
    {
    factory->DoSomething();
    }
    };
  • unique_ptr 数组如何使用

    可以创建 unique_ptr 数组,但是不能使用 make_unique 来初始化数组元素

    // Create a unique_ptr to an array of 5 integers.
    auto p = make_unique<int[]>(5);

    // Initialize the array.
    for (int i = 0; i < 5; ++i)
    {
    p[i] = i;
    wcout << p[i] << endl;
    }
  • make_unique 使用

    class Animal
    {
    private:
    std::wstring genus;
    std::wstring species;
    int age;
    double weight;
    public:
    Animal(const wstring&, const wstring&, int, double){/*...*/ }
    Animal(){}
    };

    void MakeAnimals()
    {
    // Use the Animal default constructor.
    unique_ptr<Animal> p1 = make_unique<Animal>();

    // Use the constructor that matches these arguments
    auto p2 = make_unique<Animal>(L"Felis", L"Catus", 12, 16.5);

    // Create a unique_ptr to an array of 5 Animals
    unique_ptr<Animal[]> p3 = make_unique<Animal[]>(5);

    // Initialize the elements
    p3[0] = Animal(L"Rattus", L"norvegicus", 3, 2.1);
    p3[1] = Animal(L"Corynorhinus", L"townsendii", 4, 1.08);

    // auto p4 = p2; //C2280

    vector<unique_ptr<Animal>> vec;
    // vec.push_back(p2); //C2280
    // vector<unique_ptr<Animal>> vec2 = vec; // C2280

    // OK. p2 no longer points to anything
    vec.push_back(std::move(p2));

    // unique_ptr overloads operator bool
    wcout << boolalpha << (p2 == false) << endl; // Prints "true"

    // OK but now you have two pointers to the same memory location
    Animal* pAnimal = p2.get();

    // OK. p2 no longer points to anything
    Animal* p5 = p2.release();
    }

代码解读(MSVC SDK 10.0.19041.0)

unique_ptr 是类模板,其模板参数包括底层指针的类型和 deleter,deleter 是可选参数。

unique_ptr 支持两种类型,一种是管理单个对象,一种是管理对象数组。

template <class _Ty, class _Dx /* = default_delete<_Ty> */>
class unique_ptr { // non-copyable pointer to an object

template <class _Ty, class _Dx>
class unique_ptr<_Ty[], _Dx> { // non-copyable pointer to an array object

智能指针符合 RAII。所以在构造函数中会申请资源,而在析构函数中会释放资源。unique_ptr 自身是一个类,在使用时通常会在一个域类创建栈对象,然后超出 scope 时会自动调用析构函数释放资源。

// 不带参数的构造函数
template <class _Dx2 = _Dx, _Unique_ptr_enable_default_t<_Dx2> = 0>
constexpr unique_ptr() noexcept : _Mypair(_Zero_then_variadic_args_t{}) {}

// 参数为 nullptr 的构造函数
template <class _Dx2 = _Dx, _Unique_ptr_enable_default_t<_Dx2> = 0>
constexpr unique_ptr(nullptr_t) noexcept : _Mypair(_Zero_then_variadic_args_t{}) {}

// 底层指针作为参数的构造函数
template <class _Dx2 = _Dx, _Unique_ptr_enable_default_t<_Dx2> = 0>
explicit unique_ptr(pointer _Ptr) noexcept : _Mypair(_Zero_then_variadic_args_t{}, _Ptr) {}

// 底层指针和 deleter 作为参数的构造函数,deleter 通过左值引用传入
template <class _Dx2 = _Dx, enable_if_t<is_constructible_v<_Dx2, const _Dx2&>, int> = 0>
unique_ptr(pointer _Ptr, const _Dx& _Dt) noexcept : _Mypair(_One_then_variadic_args_t{}, _Dt, _Ptr) {}

// 底层指针和 deleter 作为参数的构造函数,deleter 通过右值引用传入
template <class _Dx2 = _Dx, enable_if_t<conjunction_v<negation<is_reference<_Dx2>>, is_constructible<_Dx2, _Dx2>>, int> = 0>
unique_ptr(pointer _Ptr, _Dx&& _Dt) noexcept : _Mypair(_One_then_variadic_args_t{}, _STD move(_Dt), _Ptr) {}

// 析构函数
~unique_ptr() noexcept {
if (_Mypair._Myval2) {
_Mypair._Get_first()(_Mypair._Myval2);
}
}

因为 unique_ptr 是不能够被拷贝的,所以在代码实现上,其拷贝构造函数和拷贝赋值函数都被设置成了 delete

unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;

unique_ptr 是支持移动操作的,其内部实现了移动构造函数和移动赋值函数。

// 移动构造函数
template <class _Dx2 = _Dx, enable_if_t<is_move_constructible_v<_Dx2>, int> = 0>
unique_ptr(unique_ptr&& _Right) noexcept
: _Mypair(_One_then_variadic_args_t{}, _STD forward<_Dx>(_Right.get_deleter()), _Right.release()) {}

// 移动构造函数
template <class _Ty2, class _Dx2,..................>
unique_ptr(unique_ptr<_Ty2, _Dx2>&& _Right) noexcept
: _Mypair(_One_then_variadic_args_t{}, _STD forward<_Dx2>(_Right.get_deleter()), _Right.release()) {}

// 移动赋值函数
template <class _Dx2 = _Dx, enable_if_t<is_move_assignable_v<_Dx2>, int> = 0>
unique_ptr& operator=(unique_ptr&& _Right) noexcept {
if (this != _STD addressof(_Right)) {
reset(_Right.release());
_Mypair._Get_first() = _STD forward<_Dx>(_Right._Mypair._Get_first());
}
return *this;
}

// 移动赋值函数
template <class _Ty2, class _Dx2,..............................>
unique_ptr& operator=(unique_ptr<_Ty2, _Dx2>&& _Right) noexcept {
reset(_Right.release());
_Mypair._Get_first() = _STD forward<_Dx2>(_Right._Mypair._Get_first());
return *this;
}

unique_ptr 中只有一个成员,就是底层的指针,以下图片展示了一个指向 int 类型的一个 unique_ptr,其在 x86 编译下大小为 4。图片中箭头所指的就是 MSVC 中 unique_ptr 的底层指针。

#include <iostream>
#include <memory>

int main(void)
{
std::unique_ptr<int> p = std::make_unique<int>(10);
std::cout << sizeof(p) << '\n'; // output int x86 debug: 4
return 0;
}

unique_ptr1

对于 unique_ptr,如果 T 是某个基类 B 的派生类, std::unique_ptr 会隐式转换为 std::unique_ptr。所以默认的 deleter 会使用 B 的 delete 操作。所以为了避免未定义行为,需要将基类 B 的析构函数设置为 virtual。

unique_ptr 也可能是空指针,为了方便直接使用智能指针进行 bool 判断,unique_ptr 中定义了 operator bool。其中 _Mypair._Myval2 为底层指针。

explicit operator bool() const noexcept {
return static_cast<bool>(_Mypair._Myval2);
}

unique_ptr 可以通过 get 方法获取底层指针。通过 get_deleter 获取 deleter。

_NODISCARD pointer get() const noexcept {
return _Mypair._Myval2;
}

_NODISCARD _Dx& get_deleter() noexcept {
return _Mypair._Get_first();
}

_NODISCARD const _Dx& get_deleter() const noexcept {
return _Mypair._Get_first();
}

unique_ptr 也可以想原始指针一样,使用 * 运算符进行解引用,使用 → 运算符进行操作,其底层实现是直接将符号作用于底层指针。

_NODISCARD add_lvalue_reference_t<_Ty> operator*() const noexcept /* strengthened */ {
return *_Mypair._Myval2;
}

_NODISCARD pointer operator->() const noexcept {
return _Mypair._Myval2;
}

unique_ptr 还支持释放,重置和交换

// 释放底层指针的资源并返回资源
pointer release() noexcept {
return _STD exchange(_Mypair._Myval2, nullptr);
}

// 替换底层资源为指定资源
void reset(pointer _Ptr = nullptr) noexcept {
pointer _Old = _STD exchange(_Mypair._Myval2, _Ptr);
if (_Old) {
_Mypair._Get_first()(_Old);
}
}

// 交换两个 unique_ptr
void swap(unique_ptr& _Right) noexcept {
_Swap_adl(_Mypair._Myval2, _Right._Mypair._Myval2);
_Swap_adl(_Mypair._Get_first(), _Right._Mypair._Get_first());
}

参考链接