深入理解智能指针
专有指针
Ref: unique_ptr的使用和陷阱
一、初始化
只可以使用new来分配内存,不可 拷贝和赋值。
unique_ptr<int> up1(new int()); // okay,直接初始化
unique_ptr<int> up2 = new int(); // error! 构造函数是 explicit
unique_ptr<int> up3(up1); // error! 不允许拷贝
二、基本操作
unique_ptr<T> up
空的unique_ptr,可以指向类型为T的对象,默认使用delete来释放内存
unique_ptr<T,D> up(d)
空的unique_ptr同上,接受一个D类型的删除器d,使用删除器d来释放内存
up = nullptr
释放up指向的对象,将up置为空
up.release()
up放弃对它所指对象的控制权,并返回保存的指针,将up置为空,不会释放内存
up.reset(…)
参数可以为 空、内置指针,先将up所指对象释放,然后重置up的值.
三、参数、返回值
unique_ptr不可拷贝和赋值,那要怎样传递unique_ptr参数和返回unique_ptr呢?
事实上不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr (C++ Primer 5th p418)
-
返回值
//从函数返回一个unique_ptr
unique_ptr func1(int a)
{
return unique_ptr<int> (new int(a));
}
//返回一个局部对象的拷贝
unique_ptr func2(int a)
{
unique_ptr<int> up(new int(a));
return up;
}
-
参数
使用 “引用”,避免所有权的转移,或者暂时的移交所有权。
// 传引用
void
func1(unique_ptr<int> &up){
cout<< *up <<endl;
}
// 传值
unique_ptr
<int> func2(unique_ptr<int> up){
cout<< *up <<endl;
return up;
}
unique_ptr<int> up(new int(10));
// 传引用,不拷贝,不涉及所有权的转移
func1(up);
// 暂时转移所有权,函数结束时返回拷贝,重新收回所有权
up = func2(unique_ptr<int> (up.release()));
四、传递删除器
类似shared_ptr,用unique_ptr管理非new对象、没有析构函数的类时,需要向unique_ptr传递一个删除器。
不同的是,unique_ptr管理删除器的方式,我们必须在尖括号中unique_ptr指向类型后面提供删除器的类型,在创建或reset一个这种unique_ptr对象时,必须提供一个相同类型的可调用对象(删除器),这个删除器接受一个T*参数。
五、常见问题
Ref: 将unique_ptr传递给函数
用作参数,加上const,至少不准轻松地被修改.
但const unique_ptr<Device>&取代每一次Device*都是一个好的开始。 您显然无法复制unique_ptr并且您不想移动它。通过引用unique_ptr来替换现有函数体的主体可以继续工作。
现在有一个陷阱,您必须通过const &来阻止被调用者执行unique_ptr.reset()或unique_ptr().release()。
请注意,这仍然会将可修改的指针传递给设备。使用此解决方案,您无法轻松地将指针或引用传递给const Device。
共享指针
一、学习资源
C++中的智能指针
Ref: https://zhuanlan.zhihu.com/p/71649913
二、销毁
[ 这部分仅关于:shared_ptr ]
vector销毁,vector中的指针们所指向的各自“空间“也需要销毁。
vector的某个指针改变,相联系的指针内容全部改变。
// util/sharedptr1.cpp
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
int main()
{
// two shared pointers representing two persons by their name
shared_ptr<string> pNico(new string("nico"));
shared_ptr<string> pJutta(new string("jutta"));
// capitalize person names
(*pNico)[0] = ’N’;
pJutta->replace(0,1,"J");
// put them multiple times in a container
vector<shared_ptr<string>> whoMadeCoffee;
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
// print all elements
for (auto ptr : whoMadeCoffee) {
cout << *ptr << " ";
}
cout << endl;
// overwrite a name again
*pNico = "Nicolai";
// print all elements again
for (auto ptr : whoMadeCoffee) {
cout << *ptr << " ";
}
cout << endl;
// print some internal data
cout << "use_count: " << whoMadeCoffee[0].use_count() << endl;
}
定义销毁行为
如果某个指针要放弃某一块内存时,使用 lambda 作为构造函数的第二个参数,定义销毁操作。
当对象的最后一个指针被调用时,lambda被调用。
shared_ptr<string> pNico(new string("nico"),
[](string* p) {
cout << "delete " << *p << endl;
delete p;
}
);
...
pNico = nullptr; // pNico does not refer to the string any longer
whoMadeCoffee.resize(2); // all copies of the string in pNico are destroyed
处理数组
默认的销毁行为不会执行delete[],所以要自己定义销毁行为,例如:
std::shared_ptr<int> p(new int[10],
[](int* p) {
delete[] p; // <---- lambda
}
);
另一种方式也可以:
std::shared_ptr<int> p(new int[10],
std::default_delete<int[]>());
清理临时文件
处理清理内存以外,还有其他的资源需要处理,以下是一个清理临时文件的示例:
共享指针,指向 ”输出流“ 这个 ”对象“。
#include <string>
#include <fstream> // for ofstream
#include <memory> // for shared_ptr
#include <cstdio> // for remove()
class FileDeleter
{
private:
std::string filename;
public:
FileDeleter (const std::string& fn): filename(fn) {
}
void operator () (std::ofstream* fp) {
fp->close(); // close.file
std::remove(filename.c_str()); // delete file
}
};
int main()
{
// create and open temporary file:
std::shared_ptr<std::ofstream> fp( new std::ofstream("tmpfile.txt"), FileDeleter("tmpfile.txt") );
...
}
清理共享内存
shm_unlink主要用于linux Posix模式共享内存中的删除共享内存。
// util/sharedptr3.cpp
#include <memory> // for shared_ptr
#include <sys/mman.h> // for shared memory
#include <fcntl.h>
#include <unistd.h>
#include <cstring> // for strerror()
#include <cerrno> // for errno
#include <string>
#include <iostream>
class SharedMemoryDetacher
{
public:
void operator () (int* p) {
std::cout << "unlink /tmp1234" << std::endl;
if (shm_unlink("/tmp1234") != 0) {
std::cerr << "OOPS: shm_unlink() failed" << std::endl;
}
}
};
std::shared_ptr
<int> getSharedIntMemory(int num)
{
void* mem;
int shmfd = shm_open("/tmp1234", O_CREAT|O_RDWR, S_IRWXU|S_IRWXG);
if (shmfd < 0) {
throw std::string(strerror(errno));
}
if (ftruncate(shmfd, num*sizeof(int)) == -1) {
throw std::string(strerror(errno));
}
mem
= mmap(nullptr, num*sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, shmfd, 0);
if (mem == MAP_FAILED) {
throw std::string(strerror(errno));
}
return std::shared_ptr<int>(static_cast<int*>(mem), SharedMemoryDetacher());
}
int main()
{
// get and attach shared memory for 100 ints:
std::shared_ptr<int> smp(getSharedIntMemory(100));
// init the shared memory
for (int i = 0; i < 100; ++i) {
smp.get()[i] = i*42;
}
// deal with shared memory somewhere else:
...
std::cout << "<return>" << std::endl;
std::cin.get();
// release shared memory here:
smp.reset();
...
}
析构函数清理
当然还有一种更加清晰的实现方法:构造函数实现初始化,析构函数实现清理。这样就可以简单地使用shared_ptr管理对象。
三、初始化
(1) 为了避免隐式转换,智能指针不能使用赋值的方式初始化,使用括号初始化或者列表初始化是没有问题的。
(2) 另一种初始化的方法是使用make_shared<>,它是一种 更好且更安全 的方法:因为使用new时会创建一个对象,计算它的引用计数时会创建一个对象,而make_shared只会创建一个对象,并且不会出现控制模块失效的情况。
shared_ptr<string> p1 = make_shared<string>(10, '9');
shared_ptr<string> p2 = make_shared<string>("hello");
shared_ptr<string> p3 = make_shared<string>();
(3) 先定义一个智能指针再进行赋值。但是不能使用赋值运算符(=),必须使用reset函数。
shared_ptr<string> pNico4;
pNico4 = new string("nico"); // ERROR: no assignment for ordinary pointers
pNico4.reset(new string("nico")); // OK
四、常见问题
如果一个资源将被多层传递和访问,那么参数类型该用 weak_ptr 还是 shared_ptr?
类 weak_ptr
一、shared_ptr 的弊端
使用shared_ptr的主要原因是不想关注资源的释放。但是某些情况下shared_ptr会出现错误或者失效。
1、循环引用问题:如果两个对象使用shared_ptr指向彼此,当释放的时候,将不会自动释放资源,因为引用计数的计算有问题。
2、当共享对象的时候,指针的生命期要长于对象,因此对象不会被shared_ptr删除。指针就无法注意到对象是否释放。
如果一个资源将被多层传递和访问,那么参数类型该用 weak_ptr 还是 shared_ptr?
如何评价 C++11 的右值引用(Rvalue reference)特性?
unique_ptr相关
/* implement */
来源:oschina
链接:https://my.oschina.net/u/4363024/blog/4253683