C++智能指针(一)——shared_ptr初探

文章目录

  • 1. 普通指针存在的问题
  • 2. Class shared_ptr
    • 2.1 使用 shared_ptr
      • 2.1.1 初始化 shared_ptr
      • 2.1.2 reset
      • 2.1.3 访问数据
      • 2.1.4 use_count()
  • 3. Deleter
    • 3.1 定义一个 Deleter
    • 3.2 处理数组


1. 普通指针存在的问题

智能指针的引入,是为了解决普通指针在使用过程中存在的一些问题:其中内存泄漏以及空悬指针是最主要的问题。

正常使用普通指针,我们需要 new 分配内存,使用 delete 释放资源,一旦项目很庞大,尤其是在多个地方共享同一个指针时,产生内存泄漏的风险很大,且需要更多的代码来管理指针。

下面举一个具体实例,比如两个对象共享同一个指针,此时对于该指针在什么时候释放需要更多地代码来判断,以防止内存泄漏与访问空悬指针。

#include 
#include 
class Person
{
public:
	string name;
	Person* child;
	
	Person(const string& n, Person* c = nullptr) : name(n), child(c) {
	}
    
    ~Person() {
		std::cout << "delete" << name << std::endl;
	}
}

int main()
{
	Person* son = new Person("hhhcbw");
	Person father("c", son);
	Person mother("z", son);
	delete son;
	std::cout << father.child->name << std::endl; // ERROR: ask hanging pointer
}

为了解决普通指针的痛点,引入智能指针。


2. Class shared_ptr

shared_ptr 从字面就可以看出,该智能指针类主要用于共享资源,其能保证当最后一个对对象的引用被删除后,对象本身被删除(包括一些内存与资源的释放)。

2.1 使用 shared_ptr

使用 shared_ptr 与使用普通指针差不多。可以赋值,拷贝以及比较 shared_ptr,也可以使用操作符 *-> 来访问指针指向的对象。举一个例子:

#include 
#include 
#include 
#include 
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;
}

上面的代码,具体表现如下图所示C++智能指针(一)——shared_ptr初探_第1张图片
输出如下

Jutta Jutta Nico Jutta Nico
Jutta Jutta Nicolai Jutta Nicolai
use_count: 4

2.1.1 初始化 shared_ptr

shared_ptr 类定义在 里,需要注意的是,shared_ptr 的使用一个指针作为单独参数的构造函数是显式的(explicit),因此不能使用赋值符号,来将普通指针赋值给 shared_ptr

shared_ptr<string> pNico = new string("nico"); // ERROR
shared_ptr<string> pNico{new string("nico")}; // OK

也可以使用函数 make_shared() 来创建 shared_ptr,且这样更快且更安全:因为相比于前面的初始化的两次分配内存(一次给对象,一次给共享指针的共享数据),使用函数 make_shared() 只需要一次分配内存,完成两个步骤。

注意,尽量不要对一个已有普通指针,创建共享指针,如:

string* pNico = new string("nico");
shared_ptr<string> spNico(pNico);

如果pNico被设为nullptr,spNico.use_count()=1 且字符串未被释放,正常输出 nico
但如果spNico被设为nullptr,此时字符串被释放,但pNico还保存该地址!!

2.1.2 reset

可以先声明一个共享指针,然后给该共享指针分配一个新的指针。当然,不能使用赋值操作,要使用 reset() 方法:

shared_ptr<string> pNico4;
pNico4 = new string("nico"); // ERROR: no assignment for ordinary pointers
pNico4.reset(new string("nico")); // OK

2.1.3 访问数据

与普通指针类似,使用 *->

(*pNico)[0] = ’N’;
pJutta->replace(0,1,"J");

2.1.4 use_count()

use_count() 表示当前拥有该对象的所有共享指针的数量,当一个共享指针被删除后,use_count()-1,反之,use_count()+1

上面例子中,pJutta 本身算一个,容器 vector 里还有三个,所有 use_count() = 4


3. Deleter

当最后一个拥有者被删除后,共享指针为对象调用 delete 进行内存和资源的释放。这不一定在作用域结束处发生,比如上面的例子中,当给 pNico 赋值 nullptr 且在将 vector resize 为 2, 也会导致最后一个拥有者被删除,以至调用 delete

3.1 定义一个 Deleter

我们甚至可以自定义 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

这里传入一个lambda表达式,作为 shared_ptr 构造函数的第二个参数,当然对于任何可调用的对象都是可以的,比如函数与重载了()运算符的类与std::function,比如下面的代码就是重载了 () 运算符的类:

#include 
#include  // for ofstream
#include  // for shared_ptr
#include  // 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"));
...
}

3.2 处理数组

shared_ptr 提供的默认deleter调用 delete 而不是 delete[]。这意味着默认 deleter只有在共享指针拥有的是一个单独由new创建的对象才有效。需要注意的是,给一个数组创建共享指针是可能的,但是是错误的:

std::shared_ptr<int> p(new int[10]); // ERROR, but compiles

所以,如果要使用 new[] 来创建一个对象数组,需要定义自己的 deleter。可以传入一个函数、function object或lambda,在内部调用 delete[],例如:

std::shared_ptr<int> p(new int[10],
[](int* p) {
delete[] p;
});

也可以使用提供给 unique_ptr 的官方helper,其内部调用 delele[]

std::shared_ptr<int> p(new int[10],
std::default_delete<int[]>());

当然,unique_ptrshared_ptr 在数组的处理上有一定的区别,更详细地会在 unique_ptr 讲解

std::unique_ptr<int[]> p(new int[10]); // OK
std::shared_ptr<int[]> p(new int[10]); // ERROR: does not compile
std::unique_ptr<int,void(*)(int*)> p(new int[10],
[](int* p) {
delete[] p;
});

shared_pr 不提供操作符 []

你可能感兴趣的:(C++,#,智能指针,c++,开发语言,智能指针)