Effective C++(四): 资源管理

文章目录

  • 一、智能指针驱动的RAII
  • 二、shared_ptr 和 weak_ptr
  • 三、如何复制 RAII 对象
  • 四、在资源管理类中应该提供对原始资源的访问函数


为了防止忘记调用 delete 造成的内存泄露,我们应该尽可能让对象管理资源,并且采用 RAII 机制(Resource Acquisition is Initialize)机制,让析构函数负责资源的释放。

一、智能指针驱动的RAII

在cpp11中,可以使用unique_ptr 或者 shared_ptr两种智能指针来管理内存。其中 unique_ptr 通过专一所有权来管理 RAII 的对象,而shared_ptr通过引用计数来管理。

std::unique_ptr pUniqueInv1(CreateInvestment());
std::unique_ptr pUniqueInv2(std::move(pUniqueInv1));
std::shared_ptr pSharedInv1(CreateInvestment());

std::shared_ptr pSharedInv2(pSharedInv1); /
std::shared_ptr pSharedInv2(std::move(pSharedInv1))

std::move(pSharedInv1)返回的是pSharedInv1的右值引用,也就是一个std::shared_ptr&&类型,在执行完这句之后,pSharedInv1就变成了一个空指针 nullptr,而pSharedInv2现在拥有原本属于pSharedInv1 的对象。 请注意在调用std::shared_ptr 的移动构造函数的时候,shared_ptr的引用技术不变。

智能指针默认会自动 delete 所持有的对象,我们也可以为智能指针指定所管理对象的释放方式(删除器deleter):

// void GetRidOfInvestment(Investment*) {}

std::unique_ptr pUniqueInv(CreateInvestment(), GetRidOfInvestment);
std::shared_ptr pSharedInv(CreateInvestment(), GetRidOfInvestment);

在这里decltype的作用是:

二、shared_ptr 和 weak_ptr

一个很常见的面试问题是能否使用 weak_ptr来实现 RAII ? 答案显然是否定的。 首先介绍一下weak_ptr, weak_ptr是一种用于解决 shared_ptr的循环计数死锁的智能指针。一个例子如下:

#include 
#include 

class Child;

class Parent {
public:
    std::vector<std::shared_ptr<Child>> children;

    void addChild(const std::shared_ptr<Child>& child) {
        children.push_back(child);
    }

    ~Parent() {
        // 析构函数
    }
};

class Child {
public:
    std::shared_ptr<Parent> parent;

    Child(const std::shared_ptr<Parent>& p) : parent(p) {}

    ~Child() {
        // 析构函数
    }
};

可以看到 child 和 parent 互相持有对方的shared_ptr, 造成循环引用。 哪怕当这些对象超出作用御的时候他们的析构函数也不会被调用,从而导致内存泄露。

为了解决这个问题,我们可以使用 weak_ptr 解决循环引用,我们可以把 Child 中的shared_ptr 改成 weak_ptr :

class Child {
public:
    std::weak_ptr<Parent> parent; // 使用 weak_ptr 而非 shared_ptr

    Child(const std::shared_ptr<Parent>& p) : parent(p) {} //这里参数仍然是shapred_ptr 因为 1. shared_ptr => weak_ptr 转换是兼容的。 2.确保有一个 shared_ptr存在保证 Parent 对象存活。

    ~Child() {
        // 析构函数
    }
};

三、如何复制 RAII 对象

  1. 引用计数
    正如 shared_ptr一样,对于每一个资源对象的每一次复制就让引用计数 + 1, 每一个对象离开定义域调用析构函数使得引用计数 - 1, 直到引用计数为 0 就把资源销毁。

  2. 深拷贝
    在拷贝的时候不但copy 对象,同时 copy 底层资源,比如:
    请注意,在拷贝的时候,有三点是非常需要注意的:
    1.是否拷贝了底层资源对象
    2.是否 handle 了自赋值
    3.是否 handle 了异常处理

#include
#include

class RAIIArray { 
public:
	RAIIArray(const char* str) {
		if(str) {
			len_ = std::strlen(str) + 1;
			data_ = new char[len];
			strcpy(data_, str);
		} else {
			data_ = nullptr;
			len_ = 0;
		}
	}
	
	//Deepcopy for Copy Constructor 
	RAIIArray(const RAIIArray& other) { 
		size_ = other.size_;
		if(size_ > 0) {
			data_ = new char[size_];
			std::strcpy(data_, other.data_);
		}  else { 
			data_ = nullptr;
		}
	}
	
	//Deepcopy for operator= 
	RAIIArray& operator=(const RAIIArray& other) { 
	    //Identity Judgement
		if(this != &other) {
			delete data;
			size = other.size;
			if(size > 0) {
				data = new char[size];
				std::strcpy(data, other.data); 
			} else {
				data = nullptr;
				size = 0;
			}
		} 
		return *this;
	
	}
	//You can also use this : first explicit call copy constructor then copy & swap
	RAIIArray& operator=(const RAIIArray& other) { 
		if(this != other) {
			RAIIArray temp(other); //显shi 调用了拷贝构造函数
			swap(*this, other);
		}
		return *this;
	}
	
	//use friend to access the private variables in first and second 
	friend void swap( RAIIArray& first,  RAIIArray& second) {
		using std::swap;
		swap(first.size, second.size);
		swap(first.data, second.data);
	}
	//Deepcopy with copy-and-swap
	RAIIArray& operator=(RAIIArray other) { 
		swap(*this,other);
		return *this;
	}


}
  1. 转移底层资源所有权
    和std::unique_ptr类似,永远保持只有一个对象拥有对资源的管理权,当需要复制对象的时候转移资源的管理权。

四、在资源管理类中应该提供对原始资源的访问函数

和所有的智能指针一样,stl 中的智能指针也提供了对原始资源的隐藏访问和显示访问
Investment* pRaw = pSharedInv.get(); // 显示访问
Investment raw = *pSharedInv; //隐式访问
这里,pSharedInv是一个 shared_ptr, 指向Investment类型

当我们自己在设计自己的资源管理类的时候,也要考虑在提供原始资源访问的同时,是使用显示还是隐式方法。
比如:
class Font


你可能感兴趣的:(effective,cpp,c++)