[c++] Why should I use Smart Pointers

感情迁移 提交于 2020-04-26 14:57:45

深入理解智能指针


专有指针

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的值.
View Code

 

 

三、参数、返回值

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;
}
View Code

 

定义销毁行为

如果某个指针要放弃某一块内存时,使用 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 */

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!