智能指针的工厂函数是一个创建智能指针的函数,用于方便地创建特定类型的智能指针对象,这些函数通常都是以 make 开头的函数。比如
std::make_unique
用于创建
实例std::unique_ptr
std::make_shared
用于创建std::shared_ptr
实例
1. make_unique
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 输出结果一致。
2. make_shared
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 对象有什么不同呢?
- make_shared 是异常安全的,而构造函数创建方式并不总是异常安全
- make_shared 创建 shared_ptr 对象的效率更高
第 1 点我们在 make_unique 已做讲解。第 2 点应该怎么理解呢?
shared_ptr<Person> sp(new Person(10, 20));
当创建 sp 对象时,过程如下:
- 首先,在堆上创建 Person 动态对象,需要分配一次内存。
- 然后,在堆上创建引用计数对象,需要分配一次内存。
- 最后,在栈上创建 shared_ptr 对象。
从上面过程来看,很显然,我们需要为 Person 动态对象、引用计数对象动态分配两次内存。
而当使用 make_shared 函数时:
- 一次性分配 Person 动态对象和引用计数对象所需要的内存,并在该内存中构建 Person 对象和引用计数对象。
- 然后,在栈上创建 shared_ptr 对象。
从该过程中,我们发现 make_shared 函数会比 shared_ptr 构造函数创建方式减少一次动态内存的申请和释放,进而提升了 shared_ptr 的构建效率。