c++11 条款21:尽量使用std::make

条款21:尽量使用std::make_unique和std::make_shared而不直接使用new

让我们从对齐std::make_unique 和 std::make_shared这两块开始。std::make_shared是c++11的一部分,但是std::make_unique很可惜不是。它是在c++14里加入标准库的。加入你在使用c++11,也别担心,你很容易写出一个基本的版本。看这里:

template<typename T, typename… Ts>std::unique_ptr<T> make_unique(Ts&&… params){ return std::unique_ptr<T>(new T(std::forward<Ts>(params)…));}

正如你看到的,make_unique完美的传递了参数给对象的构造函数,从一个原始指针构造出一个std::unique,返回创建的std::unique_ptr。这个形式的函数不支持数组和定制删除器(见条款18),但它证明了一点点的努力就可以根据需要创建一个make_unique。要记住的是不要把你的版本放到std命名空间里,因为你不想当升级到c++14后会和库提供的标准实现冲突吧。

std::make_unique 和 std::make_shared是三个make函数中的两个,make函数用来把一个任意参数的集合完美转移给一个构造函数从而生成动态分配内存的对象,并返回一个指向那个对象的灵巧指针。第三个make是std::allocate_shared。它像std::make_shared一样,除了第一个参数是一个分配器对象,用来进行动态内存分配。

优先使用make函数的第一个原因即使用最简单的构造灵巧指针也能看出来。考虑如下代码:

auto upw1(std::make_unique<Widget>()); // with make funcstd::unique_ptr<Widget> upw2(new Widget); // without make funcauto spw1(std::make_shared<Widget>()); // with make funcstd::shared_ptr<Widget> spw2(new Widget); // without make func

我标注了基本的区别:

使用new的版本重复了被创建对象的键入,但是make函数则没有。重复类型违背了软件工程的一个重要原则:应该避免代码重复。。。。

优先使用make函数的第二个原因是和异常安全有关。假设我们有个函数来根据一些优先级处理一个Widget对象:

void processWidget(std::shared_ptr<Widget> spw, int priority);

对std::shared_ptr进行传值看上去有些疑问,但是条款41解释了如果processWidget始终构造一个std::shared_ptr的拷贝(比如保存在一个数据结构里,该数据结构跟踪已经被处理过的Widget对象),这可能是个合理的选择。

现在我们假设有个函数来计算相对优先级

int computePriority();

我们在一个调用processWidget时使用new而不使用std::make_shared:

processWidget(std::shared_ptr<Widget>(new Widget), // potential computePriority()); // resource // leak!

就像注释说明的,这段代码可能会因为new而引起内存泄漏。但怎么引起的呢?调用代码和被调函数都是在使用std::shared_ptr,这本来就是设计成避免内存泄漏的。它可以保证在对象不被使用时销毁所指的对象。只要一直都在使用std::shared_ptr,怎么会泄漏呢?

答案是和编译器翻译代码到目标代码有关。在运行期,传递给函数的参数必须先计算,然后才发生函数调用。因此在调用processWidget时,下面的事情必须在processWidget开始执行前发生:

1.表达式"new Widget"必须先计算,一个Widget对象必须先创建在堆上;

2.负责new出来的对象指针的std::shared_ptr<Widget>的构造函数必须执行;

3.computePriority必须运行。

没有人要求编译器必须按这样的顺序来生成代码。“new Widget”必须在std::shared_ptr构造函数前被调用,因为new的结果用作构造函数的参数,但是computePriority可以在上述调用前、后或者中间执行。也就是说编译器可能会产生这样的代码来按如下顺序执行:

1.执行“new Widget”;

2.执行computePriority;

3.调用std::shared_ptr构造函数。

假如这样的代码生成,在运行期,computePriority产生了异常,那么第一步生成的对象会被泄漏掉,因为没有在第3步被保存到std::shared_ptr。

使用std::make_shared可以避免这个问题。调用代码如下:

processWidget(std::make_shared<Widget>(), // no potential computePriority()); // resource leak

在运行期,std::make_shared或者computePriority被 首先调用。如果是std::make_shared被首先调用,指向动态内存对象的原始指针会被安全的保存在返回的std::shared_ptr对象中,然后是computePriority被调用 。如果computePriority产生了异常,那std::shared_ptr析构会知道于是它所拥有的对象会被销毁。如果computePriority先调用并产生了异常,std::make_shared不会被调用,因此也不会有动态分配的内存担心。

灯红酒绿的城市,登上楼顶,俯视万家灯火,

c++11 条款21:尽量使用std::make

相关文章:

你感兴趣的文章:

标签云: