07 Smart Pointers
STL提供了两大类smart pointer
1. shared_ptr
实现共享式拥有。多个smart pointer可以指向相同的对象, 该对象和其相关资源会在"最后一个reference"被销毁是释放。STL提供了 weak_ptr、bad_weak_ptr和enable_shared_from_this 等辅助类
2. unique_ptr
实现独占式拥有。保证同一时间内只有一个smart pointer可以指向该对象, 对于避免内存泄漏特别有用。
shared_ptr
目标: 在其所指向的对象不再被需要之后, 自动释放对象的相关资源。
可以向任何其他指针一样使用shared_ptr, 赋值、拷贝、比较, 使用 *和->方位其指向的对象
#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;
}
可以使用 make_shared() 函数
shared_ptr<string> pNico(new string("nico"));
shared_ptr<string> pJutta(new string("jutta"));
shared_ptr<string> pNico = make_shared<string>("nico");
shared_ptr<string> pJutta = make_shared<string>("jutta");
1. 定义删除方法 Deleter
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
2. 处理数组 array
shared_ptr默认提供的是delete而不是delete[], 这就表示它不能直接用来处理数组
std::shared_ptr<int> p(new int[10]); // ERROR, but compiles
对于使用 new[] 创建的对象, 需要定义一个自己的Dleter
也可以使用为 unique_ptr 而提供的辅助函数 default_delete()
std::shared_ptr<int> p(new int[10], std::default_delete<int[]>());
3. 处理其他销毁
实例一: 处理文件
#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"));
...
}
实例二: 处理共享内存
#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();
...
}
4. weak_ptr
unique_ptr
在发生异常时可以避免内存泄漏的smart指针, 可确保一个对象和其资源同一时间只被一个pointer拥有。一旦拥有者被销毁或变成empty, 或开始拥有另一个对象, 先前拥有的那个对象就会被销毁, 其任何相应资源也会被释放。
unique_ptr继承auto_ptr, auto_ptr已不再被认可
unique_ptr与普通指针相近, 使用*访问指向的对象, -> 访问对象的成员
1. 使用 unique_ptr
一般为了避免异常内存泄漏, 代码格式会繁琐
void f()
{
ClassA* ptr = new ClassA; // create an object explicitly
try {
... // perform some operations
}
catch (...) { // for any exception
delete ptr; // - clean up
throw; // - rethrow the exception
}
delete ptr; // clean up on normal end
}
如果使用 unique_ptr
void f()
{
// create and initialize an unique_ptr
std::unique<ClassA> ptr(new ClassA);
... // perform some operations
}
(1) 内容访问
// create and initialize (pointer to) string:
std::unique_ptr<std::string> up(new std::string("nico"));
(*up)[0] = ’N’; // replace first character
up->append("lai"); // append some characters
std::cout << *up << std::endl; // print whole string
(2) 该指针不提供指针算术, 例如 ++ 运算
(3) 不允许使用普通指针初始化
std::unique_ptr<int> up = new int; // ERROR
std::unique_ptr<int> up(new int); // OK
(4) 可以定义空指针
std::unique_ptr<std::string> up;
(5) 可以向bool一样判断
if (up) { // if up is not empty
std::cout << *up << std::endl;
}
if (up != nullptr) // if up is not empty
if (up.get() != nullptr) // if up is not empty
(6) 把unique_ptr作为成员
class ClassB {
private:
std::unique_ptr<ClassA> ptr1; // unique_ptr members
std::unique_ptr<ClassA> ptr2;
public:
// constructor that initializes the unique_ptrs
// - no resource leak possible
ClassB (int val1, int val2): ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {
}
// copy constructor
// - no resource leak possible
ClassB (const ClassB& x): ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) {
}
// assignment operator
const ClassB& operator= (const ClassB& x) {
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
// no destructor necessary
// (default destructor lets ptr1 and ptr2 delete their objects)
...
};
2. 数组处理
C++无法分辨指针指向的是对象还是数组, 然而, 删除对象用delete, 删除数组需要用 delete[]
下面是一个错误的定义
std::unique_ptr<std::string> up(new std::string[10]); // runtime ERROR
STL提供了一个特化版本用来处理数组
std::unique_ptr<std::string[]> up(new std::string[10]); // OK
这个版本不再使用*和->, 而是用[]
std::unique_ptr<std::string[]> up(new std::string[10]); // OK
std::cout << *up << std::endl; // ERROR: * not defined for arrays
std::cout << up[0] << std::endl; // OK
3. 其他资源的删除 Deleter
#include <iostream>
#include <string>
#include <memory> // for unique_ptr
#include <dirent.h> // for opendir(), ...
#include <cstring> // for strerror()
#include <cerrno> // for errno
using namespace std;
class DirCloser
{
public:
void operator () (DIR* dp) {
if (closedir(dp) != 0) {
std::cerr << "OOPS: closedir() failed" << std::endl;
}
}
};
int main()
{
// open current directory:
unique_ptr<DIR,DirCloser> pDir(opendir("."));
// process each directory entry:
struct dirent *dp;
while ((dp = readdir(pDir.get())) != nullptr) {
string filename(dp->d_name);
cout << "process " << filename << endl;
...
}
}