C++学习笔记----6、内存管理(五)---- 智能指针(3)

2、shared_ptr

        有时候吧,有些对象或者一部分代码需要同一个指针的拷贝。那么unique_ptr不能被拷贝,因此就不能用于些场景。这样的话,std::shared_ptr就是一个支持能够被拷贝的拥有共享属主的智能指针。但是,如果有指向同一个资源的多个shared_ptr实例,那么怎么知道什么时候去释放资源呢?这可以通过对于引用记数来解决,这个我们以后再聊。首先,让我们看一下怎么构造与使用shared_ptr。

2.1、生成与使用shared_ptr

        使用shared_ptr与unique_ptr的方式类似。使用make_shared()来生成,这比直接生成shared_ptr要有效得多。举例如下:

auto mySimpleSmartPtr { make_shared() };

        一定要使用make_shared()来生成shared_ptr。

        与unique_ptr一样,类模板参数预测对shared_ptr也不管用,所以要指定模板类型。

        make_shared()与make_unique()在值初始化方面类似。如果不想这样的话,可以使用make_shared_for_overwrite()进行缺省初始化,与make_unique_for_overwrite()可以类比。

        与unique_ptr一样,shared_ptr可以用于保存指向动态分配的C风格的数组。与使用make_unique()一样,可以使用make_shared()来干这个事儿。然而,即使可以将C风格的数组保存在share_ptr,仍然推荐使用标准库构造函数而不是C风格的数组。

        与unique_ptr一样,shared_ptr也支持get()与reset()成员函数。唯一的不同就是当调用reset()时,其关联的资源只有在最后的shared_ptr被破坏或者被重置时才会释放。注意shared_ptr不支持release()。可以使用use_count()成员函数来查询共享同样资源的shared_ptr实例的数量。

        与unique_ptr一样,shard_ptr缺省使用标准的new与delete操作符来进行内存分配与释放,或者是new[]与delete[],当保存C风格数组时。可以用如下的方式进行改变:

// Implementations of my_alloc() and my_free() as before.
shared_ptr myIntSmartPtr { my_alloc(42), my_free };

        可以看到,可以不必指出客户化的delete的类型来作为模板类型参数,这样的话,就比使用unique_ptr的客户化的delete容易些。

        下面的例子使用了shared_ptr来保存文件指针。当shared_ptr被破坏时(本例中是不在活动范围),文件指针就会自动调用close()进行关闭操作。注意c++拥有合适的面向对象的类来处理文件。这些类已经能够自动关闭文件。本例使用了旧的C风格的fopen()与fclose()函数,只是为了演示shared_ptr可以用于纯内存之外的情况。例如,如果必须要使用C风格的库的话就会很方便,因为在c++中没有替代品拥有同样的函数来打开与关闭资源。在本例中可以将其归结到shared_ptr中。

import std;
using namespace std;

void close(FILE* filePtr)
{
	if (filePtr == nullptr) { return; }
	fclose(filePtr);
	println("File closed.");
}

int main()
{
#if defined(_MSC_VER)
	// Disable the following Microsoft Visual C++ warning for this example:
	// C4996: 'fopen': This function or variable may be unsafe. Consider using fopen_s instead.
#pragma warning( disable : 4996)
#endif
	FILE* f{ fopen("data.txt","w") };
	shared_ptr filePtr{ f,close };
	if (filePtr == nullptr) {
		println(cerr, "Error opening file.");
	}
	else
	{
		println("File opened.");
	}

	return 0;
}

2.2、引用记数的必要性

        前面简要提过,当拥有共享属主的智能指针,例如shared_ptr不在活动范围或者被重置时,只有它是最后指向的智能指针时才能释放其指向的资源。这很复杂吗?有个解决方案,在shared_ptr标准库中被智能指针使用,就是引用记数。

        作为通用概念,引用记数是跟踪记录在使用中的类或者特定对象的数量的一项技术。一个引用记数的智能指针就是一个跟踪记录了许多智能指针构造用于指向一个单独的实际指针或者单独的对象。每当这样的引用记数的智能指针被拷贝,一个新的实例生成指向同样的资源,引用记数增加。当这样的智能指针不在活动范围或者被重置时,引用记数减少。当引用记数降到0时,该资源已经没有属主,智能指针会释放该资源。

        引用记数智能指针解决了许多内存管理的总是,比如双重删除。例如,假设有如下两个指向同样内存的两个原始指针。Simple类有前面讲过,只是在生成与被破坏时单纯地打印出一些信息。

Simple* mySimple1 { new Simple{} };
Simple* mySimple2 { mySimple1 }; // Make a copy of the pointer.

删除这两个原始指针就会造成双重删除:

delete mySimple2;
delete mySimple1;

        当然了,你不会这么写代码,但是这种情况是会发生的,尤其是在多重嵌套调用的函数中,一个函数删除了内存,而另一个函数先行一步,早就将其删除了。

        而如果用shared_ptr引用记数的智能指针的话,这样的双重删除就可以避免:

auto smartPtr1 { make_shared() };
auto smartPtr2 { smartPtr1 }; // Make a copy of the pointer.

        在这种情况下,当两个指针都不在活动范围或者被重置时,只有一次的Simple实例删除。

        当没有原始指针介入时,这些都管用!例如,假设你使用new分配了一些内存,然后生成了两个指向同样的原始指针的shared_ptr的实例:

Simple* mySimple { new Simple{} };
shared_ptr smartPtr1 { mySimple };
shared_ptr smartPtr2 { mySimple };

        当对象被破坏时,两个智能指针都尝试删除同样的对象。这有赖于你的编译器,这段代码可能会崩溃!如果你能得到输出,可能会像下面这样:

Simple constructor called!
Simple destructor called!
Simple destructor called!

        啊哈!调用了一次构造函数而调用了两次析构函数?对于unique_ptr也存在同样的总是。你可能会奇怪,即使是引用记数的shared_ptr类也会这么干。然而,这是正确的行为。唯一安全的方式就是简单地拷贝这样的shared_ptr来获得指向同样内存的多个shared_ptr实例。

2.3、转化shared_ptr

        与指向特定类型的原始指针可以转化为不同类型的指针一样,shared_ptr保存特定的类型可以转化为一个另一种类型的shared_ptr。当然,对于类型转化是有限制的。并不是所有的转化都是有效的。转化shared_ptr的函数是const_pointer_cast(),dynamic_pointer_cast(),static_pointer_cast()和reinterpret_pointer_cast()。其行为与工作方式与非智能指针转化函数const_cast(),dynamic_cast(),static_cast()与reinterpret_cast()一样,这些细节我们以后再讨论。

        注意这些转化只对share_ptr管用,对unique_ptr不起作用。

2.4、别名

        shared_ptr支持别名。这就允许shared_ptr通过一个拥有属主的指针与另一个指向不同对象(保存指针)的shared_ptr来共享属主。例如,它可以用于拥有一个指向对象的成员而拥有对象自身的shared_ptr。举例如下:

import std;
using namespace std;

class Foo
{
public:
	Foo(int value) : m_data{ value } { }
	int m_data;
};

int main()
{
	auto foo{ make_shared(42) };
	auto aliasing{ shared_ptr{ foo,& foo->m_data } };
}

        只有shared_ptr(foo与aliasing)都被破坏时Foo对象才会被破坏。

        拥有属主的指针用于引用计数,而保存指针在间接引用该指针或者调用get()时才会返回。

        在现代c++中,原始指针只有在没有属主介入时才会被允许!如果没有属主介入,缺省使用unique_ptr,如果属主需要共享就使用shared_ptr。还有使用make_unique()与make_shared()来生成智能指针。这样的话,就不需要直接调用new操作符,因些也不会需要调用delete。

你可能感兴趣的:(c++,学习,笔记,开发语言)