书接上回!
make_unique()使用值初始化。例如,将初始类型初始化为0,对象为缺省构造。如果不需要这样的值初始化,例如,因为不管怎么样你都会覆写共初始值,你就可以省略值初始化,通过使用make_unique_for_overwrite()函数提高性能,该函数会使用缺省初始化值。对于初始类型,这意味着它们不会被初始化,会在内存中包含任何可能的值,而对象仍会是初始构造时的值。
你也可以通过直接调用其构造函数来生成unique_ptr。注意Simple一定要用两次:
unique_ptr<Simple> mySimpleSmartPtr { new Simple{} };
我们以前讨论过,模板值预测(CTAD)通常可以用于让编译器预测出模板类型的值,基于传递给类模板的构造函数的参数。例如,允许写出vector v{1,2}而不用vector<int> v{1,2}.CTAD对于unique_ptr不好使,所以不能省略模板类型参数。
在c++17之前,只能使用make_unique()不仅仅是因为它意味着只能指定一次类型,还因为安全的原因!考虑以下对foo()函数的调用:
foo(unique_ptr<Simple> { new Simple{} }, unique_ptr<Bar> { new Bar { data() } });
如果Simple或者Bar的构造函数或者data()函数抛出了一个例外,它依赖于你的编译器的优化,Simple或者Bar对象是可能会有内存渗露的。而对于make_unique()则不会有内存渗露:
foo(make_unique<Simple>(), make_unique<Bar>(data()))
从c++17开始,对foo()的调用都是安全的,但是建议使用make_unique(),因为它易于阅读。
总是要用make_unique()来生成unique_ptr。
1.2、使用unique_ptr
标准智能指针的一个最大的特点是它提供了许多的好处,不再要求用户去学习许多的新的语法。智能指针仍然能够像标准指针一样使用间接引用(使用*或者->)。例如,还是以前的例子,->操作符用于调用go()成员函数:
mySimpleSmartPtr->go();
与标准指针一样,也可以写成如下的语句:
(*mySimpleSmartPtr).go();
get()成员函数也可以用于直接访问它包装的指针。这对于要求将指针传递给一个原始指针的情况会很有用。例如,假设你要使用如下的函数:
void processData(Simple* simple) { /* Use the simple pointer... */ }
你就可以像下面一样进行调用:
processData(mySimpleSmartPtr.get());
可以对unique_ptr包装的指针进行释放,也可以用reset()将其指向另一个指针。举例如下:
mySimpleSmartPtr.reset();// Free resource and set to nullptr
mySimpleSmartPtr.reset(new Simple{}); // Free resource and set to a new
// Simple instance
可以使用release()将unique_ptr包装的指针断开,返回包装指针指向的资源,然后将智能指针赋值为nullptr。非常有效,智能指针失去了对资源的控制,这样的话,在不再使用的时候,你就要负责将其资源进行释放!下面是一个例子:
Simple* simple { mySimpleSmartPtr.release() }; // Release ownership
// Use the simple pointer...
delete simple;
simple = nullptr;
因为unique_ptr代表了唯一的属主,所以它不能被拷贝!但是,剧透一下,使用move的语法是可以将unique_ptr move给另一个的,这个我们以后再讨论。简单提一下,std::move()工具函数可以用于显式地将unique_ptr的属主进行转移,如下面的代码片断。别担心,其语法我们以后会讨论到,你会明白的。
class Foo
{
public:
Foo(unique_ptr<int> data) : m_data { move(data) } { }
private:
unique_ptr<int> m_data;
};
auto myIntSmartPtr { make_unique<int>(42) };
Foo f { move(myIntSmartPtr) };
1.3、unique_ptr与C风格的数组
unique_ptr可以保存动态分配的旧的C风格的数组。下面的例子生成了一个包含了动态分配的C风格的十个整数的数组的unique_ptr:
auto myVariableSizedArray { make_unique<int[]>(10) };
myVariableSizedArray的类型就是unique_ptr<int []>,支持用数组下标访问其元素。如下示例:
myVariableSizedArray[1] = 123;
与非数组的情况一样,make_unique()对于数组的所有元素使用了值初始化,与std::vector类似。对于初始类型,这意味着初始化为0.如果不想生成缺省初始值的数组,可以调用make_unique_for_overwrite()函数,这意味着对于初始类型不进行初始化。要记住,要尽可能地避免不初始化数据,要理智使用。
虽然可以使用unique_ptr来保存动态分配的C风格的数组,但还是推荐使用标准构造函数,如std::array或者vector。
1.4、对delete进行客户化
缺省情况下,使用标准的new与delete操作符来分配与释放内存。但你可以改变这种行为,使用自己的分配与释放函数。当你 使用第三方C库时会很方便。例如,假设你有一个C库,要求你使用my_alloc()来分配内存,my_free()来释放内存:
int* my_alloc(int value) { return new int { value }; }
void my_free(int* p) { delete p; }
为了在对的时间在分配了的资源上正确地调用my_fee(),可以使用带有客户化了的delete的unique_ptr:
unique_ptr<int, decltype(&my_free)> myIntSmartPtr { my_alloc(42), my_free };
这段代码使用my_alloc()为整数分配了内存,通过调用my_free(),unique_ptr释放了内存。unique_ptr的这个属性对于非内存的其他资源的管理也很有用。例如,它可以用于自动关闭一个文件或者网络连接,或者任何其他资源,当unique_ptr不在活动范围时。
不幸的是,unique_ptr的客户化的delete的语法有一点儿臃肿。需要指定客户化delete的类型作为模板类型参数,它应该是指向接受一个单独的指针作为参数的指针类型,并且返回void。在这个例子中,decltype(&my_free)用于返回my_free()函数的指针类型。使用带有shared_ptr的客户化的delete会更容易一点儿。我们明天会讨论的share_ptr会展示如何使用shard_ptr来自动关闭不在活动范围的文件。