About
RSS

Bit Focus


std::unique_ptr<cookbook>

Posted at 2011-03-13 08:34:22 | Updated at 2018-12-18 18:30:48

    std::unique_ptr 是 C++11 标准的 STL 中, 用来取代 std::auto_ptr 的指针容器 (如果在代码中使用了 std::auto_ptr, 使用 gcc 4.5 之后的版本加上 -std=c++0x -Wall 参数编译时, gcc 会报出警告 std::auto_ptr 已经过时).

    同 std::auto_ptr 一样, std::unique_ptr 的基本功效也能当作简单的自动对象管理指针来使用, 如
#include <memory> /* for std::unique_ptr */
#include <iostream>

struct echo {
    echo()
    {
        std::cout << "ctor" << std::endl;
    }

    ~echo()
    {
        std::cout << "dtor" << std::endl;
    }

    echo(echo const&) = delete;
};

void test()
{
    std::unique_ptr<echo> echo_ptr(new echo);
}

int main()
{
    test();
    std::cout << "." << std::endl;
    return 0;
}
输出应该如
ctor
dtor
.
    类型 echo 在构造和析构时的输出信息可以帮助掌握对象的生命周期. 如上述程序中, echo 对象在 test 函数结束时析构.

    std::unique_ptr 的复制构造函数被 delete 处理了, 所以类似下面的代码会出错
std::unique_ptr<int> a(new int(0));
std::unique_ptr<int> b(a); // ERROR
            // deleted function
            // ‘std::unique_ptr<...>::unique_ptr(std::unique_ptr<...> const&)'
    如果确实需要复制一份整数的值, 需要可以做如下操作
std::unique_ptr<int> b(new int(*a));
    而如果是想要将 a 所持有的指针转移到 b 中, 需要显式调用 std::move 将来移交指针控制权
std::unique_ptr<int> a(new int(0));
std::unique_ptr<int> b(std::move(a));
    此时 a 中的指针会变为 NULL. 明确的转移语义由 std::move 函数表达, 与 std::auto_ptr 暧昧的构造函数不同, std::move 明确指出 std::unique_ptr 对象是通过转移构造进行参数传递的.

    当函数需要使用 std::unique_ptr 作为参数或返回值时, 可以是如下几种形式
std::unique_ptr<int>& for_reference(std::unique_ptr<int>& arg)
{
    return arg;
}

std::unique_ptr<int> for_entity(std::unique_ptr<int> arg)
{
    return std::move(arg);
}

std::unique_ptr<int>&& for_right_ref(std::unique_ptr<int>&& arg)
{
    return std::move(arg);
}

int main()
{
    std::unique_ptr<int> one(new int(1));
    for_reference(one);

    std::unique_ptr<int> three(new int(3));
    for_entity(std::move(three));
    for_entity(std::unique_ptr<int>(new int(4))); // TEMPORARY VALUE IS OK

    std::unique_ptr<int> five(new int(5));
    for_right_ref(std::move(three));

    return 0;
}
    for_reference 就没啥好说了, 就是引用传递嘛, 换成什么类型都一样. 而后面两种不同形式的, 实际上结果是相同的, 所以建议干脆干掉右值引用符号 &&, 使用 for_entity 的形式就好了.
    当需要传递参数到接受如 std::unique_ptr<int> 的实体类型的函数时, 同样要使用 std::move 将原有的栈上对象变为右值类型 (如上面代码中传递 threefor_entity), 并且此后原有的对象内部指针将被置为 NULL; 或直接使用临时值 (如上面代码中注有 TEMPORARY VALUE IS OK 的代码行).

    当需要保存函数返回的 std::unique_ptr 实例时, 可有下面几种方式
std::unique_ptr<int> mkptr(int value)
{
    return std::unique_ptr<int>(new int(value));
}

int main()
{
    std::unique_ptr<int> new_one(mkptr(19));

    std::unique_ptr<int> assigned(new int(91));
    assigned = mkptr(1729);

    return 0;
}
    其中, 当 assigned 进行赋值时, 其内部原有的指针将被执行 delete 操作.

    较 std::auto_ptr 更优越一些的是, std::unique_ptr 可以被放入容器
int main()
{
    std::vector<std::unique_ptr<int>> values;

    values.push_back(std::unique_ptr<int>(new int(0)));

    std::unique_ptr<int> two(new int(2));
    values.push_back(std::move(two));

    return 0;
}
    细心的读者一定注意到了, C++11 标准在词法分析上的一个小进步, 如上述 vector 的声明, 模板实例化在结束处的多个大于号中间不需要再加空格了.
    此外, push_back 此时接受的参数类型如之前一节中提到的 for_entity 那样, 直接调用 std::unique_ptr<int>(new int(0)) 构造一个临时值可能直接作为 push_back 的实参; 或之后在栈上构造的 two 通过 std::move 转换为一个右值后可被转移进容器.

    不仅 std::unique_ptr 本身不支持复制构造, 连带元素类型为 std::unique_ptr 的容器或者其它需要用到泛型参数复制构造函数的使用场合中, 直接复制会出错, 这时也需要连带使用转移构造, 如
void test_vector(std::vector<std::unique_ptr<int>> values)
{
    std::cout << "test_vector" << std::endl;
}

int main()
{
    std::vector<std::unique_ptr<int>> values;
    values.push_back(std::unique_ptr<int>(new int(0)));
    values.push_back(std::unique_ptr<int>(new int(1)));
    test_vector(std::move(values));
    std::cout << values.size() << std::endl;
    return 0;
}
容器在转移之后, 原容器相当于执行了清空操作, 而目标容器将持有原来的全部元素.
    因此, 如果编写兼容 C++11 的泛型容器或算法, 则可能同时考虑复制和转移的情况.

    std::unique_ptr 还可以管理对象数组, 如
int main()
{
    std::unique_ptr<echo[]> echos(new echo[2]);
    std::cout << "." << std::endl;
    return 0;
}
    实际上这是 std::unique_ptr 对对象数组类型的一个特化, 如果要定制析构方式, 指定 std::unique_ptr 的第二个参数即可, 如
struct echo {
    static echo* create()
    {
        static echo e;
        return &e;
    }

    struct deleter {
        void operator()(echo* e)
        {
            std::cout << "static instance not deleted." << std::endl;
        }
    };
private:
    echo()
    {
        std::cout << "ctor" << std::endl;
    }

    ~echo()
    {
        std::cout << "dtor" << std::endl;
    }

    echo(echo const&) = delete;
};

int main()
{
    std::unique_ptr<echo, echo::deleter> echos(echo::create());
    std::cout << "." << std::endl;
    return 0;
}
    std::unique_ptr 不是 C++ 标准委员会一时脑热加入标准库的东西, 在上面的例子中, 可以看到大量的 std::move, 这货牵扯到很多的 C++ 的转移语义 (move semantic), 而为何栈上构造的对象在转移时必须用 std::move 作显式转移, 而临时对象却可以直接传递, 这又牵扯到右值引用, 可以说这是新标准想要解决的 C++ 重大设计问题.

Post tags:   C++  C++11  unique_ptr  Move Semantic  Tutorial

Leave a comment:




Creative Commons License Your comment will be licensed under
CC-NC-ND 3.0


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site