51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

C++ 智能指针 工厂函数

智能指针的工厂函数是一个创建智能指针的函数,用于方便地创建特定类型的智能指针对象,这些函数通常都是以 make 开头的函数。比如

  1. std::make_unique 用于创建 std::unique_ptr 实例

  2. std::make_shared 用于创建 std::shared_ptr 实例

  3. make_unique {#title-0} =========================

std::make_unique 是一个 C++14 中引入的函数模板,用于创建 std::unique_ptr 实例。它的目的是简化创建动态分配对象的过程,并且提供了更好的异常安全性 。在使用 std::make_unique 时,你只需提供要动态分配的对象的类型和构造函数参数,函数会返回一个包装了这个动态分配对象的 std::unique_ptr

#if 1
#include <iostream>
using namespace std;


class Person
{
public:
	Person()
	{
		cout << "构造函数" << endl;
	}

	Person(int, int)
	{
		cout << "构造函数" << endl;
	}

	~Person()
	{
		cout << "析构函数" << endl;
	}
};

void test()
{	
	// 使用默认构造
	unique_ptr<Person> up1 = make_unique<Person>();
	// 使用有参构造
	unique_ptr<Person> up2 = make_unique<Person>(10, 20);
	// 创建对象数组
	unique_ptr<Person[]> up3 = make_unique<Person[]>(3);
}

int main()
{
	test();
	return 0;
}

#endif

由于 make_unique 函数调用对象的默认构造函数创建对象数组。所以,类内需要提供默认构造函数,否则无法使用。

template <class _Ty, enable_if_t<is_array_v<_Ty> && extent_v<_Ty> == 0, int> = 0>
unique_ptr<_Ty> make_unique(size_t _Size) { // make a unique_ptr
    using _Elem = remove_extent_t<_Ty>;
    return unique_ptr<_Ty>(new _Elem[_Size]());
}

make_unique 函数和 unique_ptr 构造函数创建智能对象时的区别是什么呢?

  1. 从语法角度:make_unique 函数比 unique_ptr 语法更加简洁
  2. 从安全角度:make_unique 是异常安全的,而 unique_ptr 构造函数创建方式则不是。

异常安全性是指当函数在执行过程中发生异常时,程序依然能够保持数据结构的一致性和资源的正确释放。对于 make_unique,它保证了在异常发生时内存会被正确释放,即使在内存分配后抛出异常也不会导致内存泄漏。

#if 1
#include <iostream>
#include <memory>
using namespace std;

class Person
{
public:
	Person()
	{
		cout << "Person 构造函数" << endl;
	}

	~Person()
	{
		cout << "Person 析构函数" << endl;
	}
};

void do_logic(unique_ptr<Person> uq, int number) {}


int get_number()
{
	cout << "get_number" << endl;
	throw exception();
	return 100;
}


int main()
{
	try
	{
		// 1. 构造函数 异常不安全
		do_logic(unique_ptr<Person>(new Person), get_number());
		// 2. make_unique 异常安全
		// do_logic(make_unique<Person>(), get_number());
	}
	catch (...)
	{
		cout << "错误处理..." << endl;
	}

	return 0;
}

#endif

程序的输入结果:

vc++:

// unique_ptr
Person 构造函数
get_number
错误处理...

// make_unique
get_number
错误处理...

gcc:

// unique_ptr
get_number
错误处理...

// make_unique
get_number
错误处理...

对于上面的运行结果,在 win 上显然会发生内存泄漏。这是因为 do_logic 函数在执行前,要先构造参数,而参数的顺序并没有规定,会导致不同的编译器不同的顺序。我们发现,make_unique 可以保证 vc++ 和 gcc 输出结果一致。

  1. make_shared {#title-1} =========================

make_shared 是 C++ 标准库提供的一个函数模板,用于创建并返回一个 std::shared_ptr,它与 new 操作符相比具有更好的性能异常安全性

class Person
{
public:
	Person(int, int) 
	{
		cout << "无参构造函数" << endl;
	}

	~Person()
	{
		cout << "析构函数" << endl;
	}
};

void test()
{
	shared_ptr<Person> sp = make_shared<Person>(10, 20);
}

注意make_shared 函数并不支持创建用于管理动态对象数组的 shared_ptr 对象,这个和 make_unique 是有区别的。

那么,make_shared 函数和 shared_ptr 构造函数创建的 shared_ptr 对象有什么不同呢?

  1. make_shared 是异常安全的,而构造函数创建方式并不总是异常安全
  2. make_shared 创建 shared_ptr 对象的效率更高

第 1 点我们在 make_unique 已做讲解。第 2 点应该怎么理解呢?

shared_ptr<Person> sp(new Person(10, 20));

当创建 sp 对象时,过程如下:

  1. 首先,在堆上创建 Person 动态对象,需要分配一次内存。
  2. 然后,在堆上创建引用计数对象,需要分配一次内存。
  3. 最后,在栈上创建 shared_ptr 对象。

从上面过程来看,很显然,我们需要为 Person 动态对象、引用计数对象动态分配两次内存。

而当使用 make_shared 函数时:

  1. 一次性分配 Person 动态对象和引用计数对象所需要的内存,并在该内存中构建 Person 对象和引用计数对象。
  2. 然后,在栈上创建 shared_ptr 对象。

从该过程中,我们发现 make_shared 函数会比 shared_ptr 构造函数创建方式减少一次动态内存的申请和释放,进而提升了 shared_ptr 的构建效率。



赞(4)
未经允许不得转载:工具盒子 » C++ 智能指针 工厂函数