c++11智能指针
auto_ptr
template< class T > class auto_ptr;
(1) (deprecated in C++11) (removed in C++17)
template<> class auto_ptr<void>;
(2) (deprecated in C++11) (removed in C++17)
auto_ptr这个智能指针已经被弃用了,但是还是要看一看它为什么被弃用了?它的缺点是什么?以便于更好的理解后续的智能指针!
代码示例:
auto_ptr<int> sp1(new int(10));
auto_ptr<int> sp2(sp1);
if (sp1.get() != nullptr)
{
cout << "sp1 is not empty!" << endl;
}
else
{
cout << "sp1 is empty!" << endl;
}
if (sp2.get() != nullptr)
{
cout << "sp2 is not empty!" << endl;
}
else
{
cout << "sp2 is empty!" << endl;
}
auto_ptr<int> sp3(new int(10));
auto_ptr<int> sp4;
sp4 = sp3;
if (sp3.get() != nullptr)
{
cout << "sp3 is not empty!" << endl;
}
else
{
cout << "sp3 is empty!" << endl;
}
if (sp4.get() != nullptr)
{
cout << "sp4 is not empty!" << endl;
}
else
{
cout << "sp4 is empty!" << endl;
}
运行结果:
sp1 is empty!
sp2 is not empty!
sp3 is empty!
sp4 is not empty!
可以通过结果看到,**在复制前sp1和sp3所托管的内存空间再复制后发生了转移!**显然在一些容器中使用auto_ptr会造成很多问题,如对容器中的元素赋值传递,这样会造成很多元素被置为空指针,造成错误!c++11标准引入了三种新型的智能指针:unique_ptr、shared_ptr、weak_ptr!
unique_ptr
unique_ptr 对其持有的堆内存具有唯一拥有权,也就是说该智能指针对资源(即其管理的堆内存)的引用计数永远是 1,unique_ptr 对象在销毁时会释放其持有的堆内存。
对unique_ptr初始化常见的方式
unique_ptr<int> sp1(new int(10));
unique_ptr<int> sp2;
sp2.reset(new int(10));
unique_ptr<int>sp3 = make_unique<int>(10); (c++14)
推荐使用方式三,因为方式三更加安全。如果不支持c++14,当然也可以自己实现方式三:
template<typename Tp, typename... Args>
make_unique(Args&&... args)
{ return unique_ptr<Tp>(new Tp(std::forward<Args>(args)...)); }
鉴于auto_ptr的前车之鉴,unique_ptr禁止复制语义,为了达到效果,unique_ptr的拷贝构造函数和复制运算符函数均被标记为=delete!
代码示例:
unique_ptr<int> sp1 = make_unique<int>(10);
unique_ptr<int> sp2(sp1);
unique_ptr<int> sp3;
sp3 = sp1;
不过禁止复制语义也存在特例,例如可以通过一个函数返回一个unique_ptr
unique_ptr<int> func(int val)
{
unique_ptr<int> sp1 = make_unique<int>(10);
return sp1;
}
int main()
{
unique_ptr<int> sp = func(1);
return 0;
}
虽然unique_ptr不能被赋值,但是也可以将所持有的堆内存转移给另外一个unique_ptr。就是使用std::move
代码示例:
unique_ptr<int> sp1 = make_unique<int>(10);
unique_ptr<int> sp2(move(sp1));
unique_ptr<int> sp3;
sp3 = move(sp2);
if (sp1.get() == nullptr)
{
cout << "sp1 is empty!" << endl;
}
else
{
cout << "sp1 is not empty!" << endl;
}
move将sp1所持有的堆内存(值为10)转移给sp2,在将sp2转移给sp3。最后sp1、和sp2不在持有堆内存的引用,变成一个空的智能指针对象。当然并不是所有对象move的操作都有意义,只有实现了移动构造函数或者移动赋值运算符(operator=)的类才可以,而unique_ptr正好实现了二者。
unique_ptr还可以持有一组堆对象,示例如下:
unique_ptr<int[]> sp1(new int[10]);
unique_ptr<int[]> sp2;
sp2.reset(new int[10]);
unique_ptr<int[]> sp3(make_unique<int[]>(10));
for (int i = 0; i < 10; ++i)
{
sp1[i] = i;
sp2[i] = 2 * i;
sp3[i] = 3 * i;
}
for (int i = 0; i < 10; ++i)
{
cout << sp1[i] << ", " << sp2[i] << ", " << sp3[i] << endl;
}
在默认情况下,智能指针对象在析构时只会释放其持有的堆内存,但是加入这块内存代表的对象还对应一种需要回收的资源(如socket、文件句柄等),我们可以通过给智能指针自定义回收函数来实现资源回收。
unique_ptr的自动释放语法规则:
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr; (1) (since C++11)
template <
class T,
class Deleter
> class unique_ptr<T[], Deleter>; (2) (since C++11)
T:需要释放的对象类型
Deleter:自定义的函数指针
示例代码:
class Socket
{
public:
Socket() {}
~Socket() {}
void close() {}
};
int main()
{
auto deletor = [](Socket *pSocket)
{
pSocket->close();
delete pSocket;
};
unique_ptr<Socket, void (*)(Socket * pSocket)> sp(new Socket(), deletor);
unique_ptr<Socket, decltype(deletor)> sp(new Socket(), deletor);
return 0;
}
shared_ptr
? unique_ptr对其持有的资源具有独占性,而shared_ptr持有的资源可以在多个shared_ptr之间共享,每多一个shared_ptr对资源的引用,资源的引用计数就会增加1,在每一个指向该资源的shared_ptr对象析构时,资源引用计数都会-1,最后一个shared_ptr对象析构时,若发现资源引用计数为0,那么将释放所持有的的资源。
? 多个线程之间递增和减少资源的引用计数是安全的,但并不意味着多个线程同时操作shared_ptr管理的资源是安全的,shared_ptr提供了一个use_count方法来获取当前管理的资源的引用计数。
初始化shared_ptr示例:
方式一:
shared_ptr<int> sp1(new int(123));
方式二
shared_ptr<int> sp2;
sp2.reset(new int(123));
方式三(优先使用)
shared_ptr<int> sp3;
sp3 = make_shared<int>(123);
使用示例:
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
void test()
{
shared_ptr<A> sp1(new A());
cout << "sp1 use count = " << sp1.use_count() << endl;
cout << "--将sp1托管的对象拷贝给sp2--" << endl;
shared_ptr<A> sp2(sp1);
cout << "sp1 use count = " << sp1.use_count() << endl;
cout << "sp2 use count = " << sp2.use_count() << endl;
cout << "--sp2释放对资源对象A的引用--" << endl;
sp2.reset();
cout << "sp1 use count = " << sp1.use_count() << endl;
cout << "sp2 use count = " << sp2.use_count() << endl;
cout << "--将sp1赋值给sp1--" << endl;
shared_ptr<A> sp3 = sp1;
cout << "sp1 use count = " << sp1.use_count() << endl;
cout << "sp3 use count = " << sp3.use_count() << endl;
}
int main(int argc, char **argv)
{
test();
return 0;
}
运行结果:
A()
sp1 use count = 1
--将sp1托管的对象拷贝给sp2--
sp1 use count = 2
sp2 use count = 2
--sp2释放对资源对象A的引用--
sp1 use count = 1
sp2 use count = 0
--将sp1赋值给sp1--
sp1 use count = 2
sp3 use count = 2
~A()
shared_ptr循环引用问题,代码示例
#include <iostream>
#include <memory>
using namespace std;
class B;
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
shared_ptr<B> sp1;
};
class B
{
public:
B()
{
cout << "B()" << endl;
}
~B()
{
cout << "~B()" << endl;
}
shared_ptr<A> sp2;
};
int main(int argc, char **argv)
{
test1();
return 0;
}
运行结果:
A()
B()
可以看到只有两个对象的构造函数调用,没有出现析构函数,说明此时造成了内存泄漏。为什么会导致这种问题呢。因为shared_ptr是强的智能指针,两个对象都互相指向,两个对象任意销毁都需要先销毁另外一个对象,造成引用计数永远不为0,所以造成了内存泄漏。为此c++11中还引入了weak_ptr来解决这种问题。
weak_ptr
weak_ptr是一个不控制资源生命周期的智能指针,是对对象的一种弱引用,只提供对其管理的资源一个访问手段,它出现的目的就是为了协助shared_ptr。
使用代码示例:
#include <iostream>
#include <memory>
using namespace std;
void test()
{
shared_ptr<int> sp1(new int(123));
cout << "sp1.use_count = " << sp1.use_count() << endl;
weak_ptr<int> sp2(sp1);
cout << "sp1.use_count = " << sp1.use_count() << endl;
cout << "sp2.use_count = " << sp2.use_count() << endl;
weak_ptr<int> sp3 = sp1;
cout << "sp1.use_count = " << sp1.use_count() << endl;
cout << "sp3.use_count = " << sp3.use_count() << endl;
weak_ptr<int> sp4(sp2);
cout << "sp2.use_count = " << sp2.use_count() << endl;
cout << "sp4.use_count = " << sp4.use_count() << endl;
}
int main(int argc, char **argv)
{
test();
return 0;
}
运行结果:
sp1.use_count = 1
sp1.use_count = 1
sp2.use_count = 1
sp1.use_count = 1
sp3.use_count = 1
sp2.use_count = 1
sp4.use_count = 1
weak_ptr不管理所引用的资源的生命周期,所以引用的资源可能在某个时刻失效,所以我们在使用weak_ptr引用资源时,需要知道该资源是否有效。可以使用expired来检测,如果返回的是truc,表示引用的资源已经失效,返回false表示资源仍有效。此时可以使用lock方法来讲weak_ptr转换到shared_ptr对象后继续操作该资源。
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
void test()
{
weak_ptr<A> wp;
{
shared_ptr<A> sp(new A());
wp = sp;
if (wp.expired())
{
cout << "wp托管的资源已被销毁!" << endl;
}
shared_ptr<A> sp1 = wp.lock();
if (sp1)
{
cout << "sp1.use_count = " << sp1.use_count() << endl;
}
else
{
cout << "weak_ptr所托管的对象已经被销毁!" << endl;
}
}
cout << "---------------------" << endl;
if (wp.expired())
{
cout << "wp托管的资源已被销毁!" << endl;
}
shared_ptr<A> sp1 = wp.lock();
if (sp1)
{
cout << "sp1.use_count = " << sp1.use_count() << endl;
}
else
{
cout << "weak_ptr所托管的对象已经被销毁!" << endl;
}
}
int main(int argc, char **argv)
{
test();
return 0;
}
处理shared_ptr循环引用问题时,只需要将一个类中的指针改成weak_ptr即可。
智能指针的大小
需要注意的是,有同学看到指针,64位系统中直接就认为8个字节。其实不然,拿我本机测试(gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04))
unique_ptr<int> up;
shared_ptr<int> sp;
weak_ptr<int> wp;
cout << "sizeof(unique_ptr) = " << sizeof(up) << endl;
cout << "sizeof(shared_ptr) = " << sizeof(sp) << endl;
cout << "sizeof(weak_ptr) = " << sizeof(wp) << endl;
输出结果:
sizeof(unique_ptr) = 8
sizeof(shared_ptr) = 16
sizeof(weak_ptr) = 16
可以看到shared_ptr和weak_ptr大小是原始指针的二倍。unique_ptr和和原始指针一样大。
智能指针的使用注意事项
-
使用智能指针托管一个对象后,不要使用原始指针去操作它,错误案例 #include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
A *pa = new A();
unique_ptr<A> up(pa);
delete pa;
return 0;
}
运行结果:
A()
~A()
~A()
free(): double free detected in tcache 2
已放弃 (核心已转储)
除了使用三个智能指针的get()方法来获取原始指针。 -
不同场景下使用的指针
- 如不需要共享资源,那么优先适应unique_ptr,反之使用shared_ptr。
- 如果不需要管理对象的生命周期,使用weak_ptr。
-
避免操作某个引用资源已经释放的智能指针 -
作为类成员变量,应该优先使用前置声明
如果本文对你有帮助,记得一键三连哦,一键三连笑哈哈,代码能力顶呱呱!
本人能力有限,如有错误,望不吝指正;原创不易,欢迎转载,转载请注明出处!
|