智能指针的工厂函数是一个创建智能指针的函数,用于方便地创建特定类型的智能指针对象,这些函数通常都是以 make 开头的函数。比如
-
std::make_unique
用于创建std::unique_ptr
实例 -
std::make_shared
用于创建std::shared_ptr
实例 -
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 构造函数创建智能对象时的区别是什么呢?
- 从语法角度:make_unique 函数比 unique_ptr 语法更加简洁
- 从安全角度: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 输出结果一致。
- 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 对象有什么不同呢?
- 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 的构建效率。