程序员,别再吐槽c++中的指针了,学会使用智能指针
使用std::unique_ptr代替new operator
这是个开始,就让我们使用std::unique_ptr代替new operator吧!
还是用程序说话:
#include<iostream>
int main()
{
while (true)
int *x = new int;
}
看下任务管理器中的内存:
此时使用智能指针unique_ptr:
#include<iostream>
#include<memory>
int main()
{
while (true)
std::unique_ptr<int> x(new int(10));
}
两张图片就可以清晰看到智能指针带来的好处。
如果我们对传统的指针及时的释放,我们也可以达到智能指针的效果:
#include <iostream> using namespace std; int main() { while (true) { int *x = new int; delete x;
这里再分享一个错误,在MAC编辑器上写的C++代码,其中用到了智能指针unique_ptr,这个代码在vs2015中时候的时候,就会提示错误:
‘unique_ptr’ is not a member of ‘std’
原因很简单,就是缺少unique_ptr的头文件:
#include<memory>
那么你也许会问题,智能指针是何时释放内存的呢?
unique_ptr objects automatically delete the object they manage (using a deleter) as soon as they themselves are destroyed, or as soon as their value changes either by an assignment operation or by an explicit call to unique_ptr::reset.
std::unique_ptr的构造(尽量使用C++14中的std::make_unique,而不是new)
这些构造方法:
default (1)
constexpr unique_ptr() noexcept;
from null pointer (2)
constexpr unique_ptr (nullptr_t) noexcept : unique_ptr() {}
from pointer (3)
explicit unique_ptr (pointer p) noexcept;
from pointer + lvalue deleter (4)
unique_ptr (pointer p,
typename conditional<is_reference<D>::value,D,const D&> del) noexcept;
from pointer + rvalue deleter (5)
unique_ptr (pointer p,
typename remove_reference<D>::type&& del) noexcept;
move (6)
unique_ptr (unique_ptr&& x) noexcept;
move-cast (7)
template <class U, class E>
unique_ptr (unique_ptr<U,E>&& x) noexcept;
move from auto_ptr (8)
template <class U>
unique_ptr (auto_ptr<U>&& x) noexcept;
copy (deleted!) (9)
unique_ptr (const unique_ptr&) = delete;
下面就是各个构造unique_ptr的方法:
先看看1 2 3 4 5 8
#include <iostream>
#include <memory>
int main() {
std::default_delete<int> d;
std::unique_ptr<int> u1;
std::unique_ptr<int> u2(nullptr);
std::unique_ptr<int> u3(new int);
std::unique_ptr<int> u4(new int, d);
std::unique_ptr<int> u5(new int, std::default_delete<int>());
std::unique_ptr<int> u8(std::auto_ptr<int>(new int));
std::cout << "u1: " << (u1 ? "not null" : "null") << '';
if (u1 != nullptr)
{
std::cout <<"*u1: "<< *u1 << std::endl;
}
std::cout << "u2: " << (u2 ? "not null" : "null") << '';
if (u2 != nullptr)
{
std::cout << "*u2: " << *u2 << std::endl;
}
std::cout << "u3: " << (u3 ? "not null" : "null") << '';
if (u3 != nullptr)
{
std::cout << "*u3: " << *u3 << std::endl;
}
std::cout << "u4: " << (u4 ? "not null" : "null") << '';
if (u4 != nullptr)
{
std::cout << "*u4: " << *u4 << std::endl;
}
std::cout << "u5: " << (u5 ? "not null" : "null") << '';
if (u5 != nullptr)
{
std::cout << "*u5: " << *u5 << std::endl;
}
std::cout << "u8: " << (u8 ? "not null" : "null") << '';
if (u8 != nullptr)
{
std::cout << "*u8: " << *u8 << std::endl;
}
return 0;
}
输出如下:
u1: null
u2: null
u3: not null
*u3: -842150451
u4: not null
*u4: -842150451
u5: not null
*u5: -842150451
u8: not null
*u8: -842150451
分析可以看出构造函数1 2两个构造方法等价,就是nullptr, 而其他的都是垃圾值。
构造函数3 也许是我们最熟悉的 形如 int* p = new int;
这里的构造函数4 5都用到了一个std::default_delete,这个是什么鬼?
std::default_delete is the default destruction policy used by std::unique_ptr when no deleter is specified.
1) The non-specialized default_delete uses delete to deallocate memory for a single object.
2) A partial specialization for array types that uses delete[] is also provided.
构造函数8 就是从其他智能指针进行构造unique_ptr.
接下来就剩下构造方法6和7个,都使用了std::move语义,我们加上,进行演示:
#include <iostream>
#include <memory>
int main() {
std::default_delete<int> d;
std::unique_ptr<int> u1;
std::unique_ptr<int> u2(nullptr);
std::unique_ptr<int> u3(new int);
std::unique_ptr<int> u4(new int, d);
std::unique_ptr<int> u5(new int, std::default_delete<int>());
std::unique_ptr<int> u8(std::auto_ptr<int>(new int));
std::cout << "u1: " << (u1 ? "not null" : "null") << '';
if (u1 != nullptr)
{
std::cout <<"*u1: "<< *u1 << std::endl;
}
std::cout << "u2: " << (u2 ? "not null" : "null") << '';
if (u2 != nullptr)
{
std::cout << "*u2: " << *u2 << std::endl;
}
std::cout << "u3: " << (u3 ? "not null" : "null") << '';
if (u3 != nullptr)
{
std::cout << "*u3: " << *u3 << std::endl;
}
std::cout << "u4: " << (u4 ? "not null" : "null") << '';
if (u4 != nullptr)
{
std::cout << "*u4: " << *u4 << std::endl;
}
std::cout << "u5: " << (u5 ? "not null" : "null") << '';
if (u5 != nullptr)
{
std::cout << "*u5: " << *u5 << std::endl;
}
std::cout << "u8: " << (u8 ? "not null" : "null") << '';
if (u8 != nullptr)
{
std::cout << "*u8: " << *u8 << std::endl;
}
std::unique_ptr<int> u6(std::move(u5));
std::cout << "u6: " << (u6 ? "not null" : "null") << '';
if (u6 != nullptr)
{
std::cout << "*u6: " << *u6 << std::endl;
}
std::cout << "now, let us see u5:";
std::cout << "u5: " << (u5 ? "not null" : "null") << '';
if (u5 != nullptr)
{
std::cout << "*u5: " << *u5 << std::endl;
}
std::unique_ptr<int> u7(std::move(u6));
std::cout << "u7: " << (u7 ? "not null" : "null") << '';
if (u7 != nullptr)
{
std::cout << "*u7: " << *u7 << std::endl;
}
std::cout << "now, let us see u6:";
std::cout << "u6: " << (u6 ? "not null" : "null") << '';
if (u6 != nullptr)
{
std::cout << "*u6: " << *u6 << std::endl;
}
return 0;
}
//输出:
// u1 : null
// u2 : null
// u3 : not null
// *u3 : -842150451
// u4 : not null
// *u4 : -842150451
// u5 : not null
// *u5 : -842150451
// u8 : not null
// *u8 : -842150451
// u6 : not null
// *u6 : -842150451
// now, let us see u5 : u5 : null
// u7 : not null
// *u7 : -842150451
// now, let us see u6 : u6 : null
这里最最想说明的就是:
u5原来不是null,但是std::move后,u5就变为了空,
u6最开始也不为空,std::move后,u6也变为了空。
还需要强调的一点,就是在对指针进行解除引用的时候,一定要验证指针是否为空,如果指针为空,再进行取值操作,程序就会崩溃。严重的bug。
如果我写到现在就停下来,你又会说我是标题党,但是我不是。
继续:
std::make_unique没有纳入C++11,是C++14的内容:
构造函数有三个:
template< class T, class… Args >
unique_ptr make_unique( Args&&… args );
template< class T >
unique_ptr make_unique( std::size_t size );
template< class T, class… Args >
/* unspecified */ make_unique( Args&&… args ) = delete;
#include <iostream>
#include <memory>
struct Vec3
{
int x, y, z;
Vec3() : x(0), y(0), z(0) { }
Vec3(int x, int y, int z) :x(x), y(y), z(z) { }
friend std::ostream& operator<<(std::ostream& os, Vec3& v) {
return os << '{' << "x:" << v.x << " y:" << v.y << " z:" << v.z << '}';
}
};
int main()
{
// Use the default constructor.
std::unique_ptr<Vec3> v1 = std::make_unique<Vec3>();
// Use the constructor that matches these arguments
std::unique_ptr<Vec3> v2 = std::make_unique<Vec3>(0, 1, 2);
// Create a unique_ptr to an array of 5 elements
std::unique_ptr<Vec3[]> v3 = std::make_unique<Vec3[]>(5);
std::cout << "make_unique<Vec3>(): " << *v1 << ''
<< "make_unique<Vec3>(0,1,2): " << *v2 << ''
<< "make_unique<Vec3[]>(5): " << '';
for (int i = 0; i < 5; i++) {
std::cout << " " << v3[i] << '';
}
}
//输出:
//make_unique<Vec3>() : {x:0 y : 0 z : 0}
//make_unique<Vec3>(0, 1, 2) : {x:0 y : 1 z : 2}
//make_unique<Vec3[]>(5) :
//{x:0 y : 0 z : 0}
//{x:0 y : 0 z : 0}
//{x:0 y : 0 z : 0}
//{x:0 y : 0 z : 0}
//{x:0 y : 0 z : 0}
正如你看到的,make_unique完美的传递了参数给对象的构造函数,从一个原始指针构造出一个std::unique,返回创建的std::unique_ptr。这个形式的函数不支持数组和定制删除器
同直接使用new相比,make函数减小了代码重复,提高的异常安全,并且对于std::make_shared和std::allcoated_shared,生成的代码会更小更快。
unique_ptr的operator=、operator bool、reset、swap、get等介绍
既然打算把unique_ptr写一个系列,就要详尽一点,有些内容也许在vector的时候有个涉及,但是现在还是再谈论一番。
我们要把unique_ptr看做一个类,废话了,它当然是一个类。所以这个类肯定也重载了赋值运算符,即operator=。现在就开始看看operator=在unique_ptr中的使用:
官方描述如下:
move assignment (1)
unique_ptr& operator= (unique_ptr&& x) noexcept;
assign null pointer (2)
unique_ptr& operator= (nullptr_t) noexcept;
type-cast assignment (3)
template <class U, class E>
unique_ptr& operator= (unique_ptr<U,E>&& x) noexcept;
copy assignment (deleted!) (4)
unique_ptr& operator= (const unique_ptr&) = delete;
这里简要实例:
#include <iostream>
#include <memory>
int main () {
std::unique_ptr<int> foo;
std::unique_ptr<int> bar;
foo = std::unique_ptr<int>(new int (101)); // rvalue
bar = std::move(foo); // using std::move
std::cout << "foo: ";
if (foo) std::cout << *foo << ''; else std::cout << "empty";
std::cout << "bar: ";
if (bar) std::cout << *bar << ''; else std::cout << "empty";
return 0;
}
输出:
foo: empty
bar: 101
唯一需要说明的就是std::move后,foo变为了empty,如果对于这个empty再做一些操作的话,就会导致一些灾难了。
其实说白了,就是摧毁原来的指针,并把新指针指向原来指针指向的对象。。。。
那么接下来reset方法即出场了,到达上面的效果:
#include <iostream>
#include <memory>
int main () {
std::unique_ptr<int> up; // empty
up.reset (new int); // takes ownership of pointer
*up=5;
std::cout << *up << '';
up.reset (new int); // deletes managed object, acquires new pointer
*up=10;
std::cout << *up << '';
up.reset(); // deletes managed object
return 0;
}
//输出:
5
10
swap
我们在vector里就接触过,unique_ptr同样具有这个成员函数:
Exchanges the contents of the unique_ptr object with those of x, transferring ownership of any managed object between them without destroying either.
get
get很简单,唯一需要注意的就是:
Notice that a call to this function does not make unique_ptr release ownership of the pointer (i.e., it is still responsible for deleting the managed data at some point). Therefore, the value returned by this function shall not be used to construct a new managed pointer.
release
不必多说:
This call does not destroy the managed object, but the unique_ptr object is released from the responsibility of deleting the object. Some other entity must take responsibility for deleting the object at some point.
unique_ptr的get_deleter方法(自定义删除器)
unique_ptr的成员函数在上一篇博客中几乎全部涵盖,其实还有一个很有踢掉,即std::unique_ptr::get_deleter
字面已经很明显了,就获得deleter:
Returns the stored deleter
The stored deleter is a callable object. A functional call to this object with a single argument of member type pointer is expected to delete the managed object, and is automatically called when the unique_ptr is itself destroyed, assigned a new value, or resetted while non-empty.
使用unique_ptr来避免if多层嵌套
我们太喜欢流程控制了,在程序中写了太多的if else
也许我们对于逻辑非常的清晰,但是对于阅读你代码的人来说就是一场灾难。
很多人都说使用多态来避免过多的if else嵌套,但是有时候你会觉得新写一个类似乎有点小题大做,尤其在整个代码已经庞大的时候。
还有人说,可以把if else语句转换为switch语句,这样也可以适当的避免多层的if嵌套。
实战过程中,下面这个场景太熟悉不过了:
先创建一个对象A,然后再用这个对象去创建另外一个对象B。
bool Init(){
A* pa = nullptr;
B* pb = nullptr;
pa = CreateA();
if (pa) {
if (doStuffByA(pa)) {
pb = CreateBbyA(pa);
if (pb) {
if (doStuffByB(pb)) {
}
else {
DestoryB(pb);
DestoryA(pa);
return false;
}
}
else {
DestoryA(pa);
return false;
}
}
else {
DestoryA(pa);
return false;
}
}
else{
return false;
}
return true;
}
C++11下,我们有了利器unique_ptr
bool Init4()
{
auto deleterA = [&](A* p){ if (p) DestoryA(p); };
std::unique_ptr<A, decltype(deleterA)> pa(CreateA(),deleterA);
if (!pa){
return false;
}
if (doStuffByA(pa.get())){
return false;
}
auto deleterB = [&](B* p){ if (p) DestoryB(p); };
std::unique_ptr<B, decltype(deleterB)> pb(CreateBbyA(pa.get()), deleterB);
if (!pb){
return false;
}
if (doStuffByB(pb.get())){
return false;
}
pa.release();
pb.release();
return true;
}
通过unique_ptr对shared_ptr进行初始化
#include <iostream>
#include <memory>
int main()
{
std::cout << "start!";
auto customArrayAllocator = [](unsigned int num){
std::cout << "custom array allocator";
return new int[num];
};
std::cout << "allocator constructed";
auto customArrayDeleter = [](int *ptr){
std::cout << "custom array deleter";
delete[] ptr;
};
std::cout << "deleter constructed";
std::unique_ptr<int[], decltype(customArrayDeleter)>
myUnique(customArrayAllocator(4), customArrayDeleter);
std::cout << "unique_ptr constructed";
std::shared_ptr<int>
myShared = std::move(myUnique);
std::cout << "shared_ptr constructed";
}