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
将原有的栈上对象变为右值类型 (如上面代码中传递 three
到 for_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;
}
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++ 重大设计问题.