C++11系列 make_shared

make_shared

定义于头文件  <memory>
  
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
(1)(C++11 起) 
(T 非数组)
template<class T>
shared_ptr<T> make_shared(std::size_t N);
(2)(C++20 起) 
(T 为 U[])
template<class T>
shared_ptr<T> make_shared();
(3)(C++20 起) 
(T 为 U[N])
template<class T>
shared_ptr<T> make_shared(std::size_t N, const std::remove_extent_t<T>& u);
(4)(C++20 起) 
(T 为 U[])
template<class T>
shared_ptr<T> make_shared(const std::remove_extent_t<T>& u);
(5)(C++20 起) 
(T 为 U[N])
  


    1)  以  args  为  T  的构造函数参数列表,构造  T  类型对象并将它包装于  std::shared_ptr  。对象如同用表达式  ::new (pv) T(std::forward<Args>(args)...)  构造,其中  pv  是内部指向适合保有  T  类型对象的存储的  void*  指针。存储典型地大于  sizeof(T)  ,以对共享指针控制块和  T  对象使用一次分配。此函数所调用的  std::shared_ptr  构造函数以指向新构造的  T  类型对象指针启用  shared_from_this  。此重载仅若 T 不是数组类型才参与重载决议。
    2,3) 同  (1) ,但构造的元素是可能多维的数组,其  std::remove_all_extents_t<T> 类型的非数组元素如同以布置 new 表达式  ::new(pv) std::remove_all_extents_t<T>() 值初始化。重载  (2) 创建第一维上大小为  N 的数组。数组元素以其地址递增顺序初始化,而当其生存期结束时,以原本构造顺序的逆序销毁。
         4,5) 同  (2,3) ,但每个元素从默认值  u 初始化。若  U 不是数组类型,则这如同以如  (1) 中的布置 new 表达式进行;否则,这如同以如同  (1) 中的布置 new 表达式,从来自  u 的对应元素初始化(可能多维的)数组的每个非数组元素。重载  (4) 创建第一维上大小为  N 的数组。数组元素以其地址递增顺序初始化,而当其生存期结束时,以原本构造顺序的逆序销毁。

参数

args-将用以构造 T 实例的参数列表。
N-要使用的数组
u-初始化数组每个元素的初值

返回值

类型 T 实例的 std::shared_ptr 。

异常

可能抛出 std::bad_alloc 或任何 T 构造函数所抛的异常。若抛出异常,则函数无效果。若异常在数组的构造中抛出,则已初始化元素以逆序销毁。 (C++20 起)

注意

此函数可用作 std::shared_ptr<T>(new T(args...)) 的替代品。得失是:

  • std::shared_ptr<T>(new T(args...)) 进行至少二次分配(一次为 T 而另一次为共享指针的控制块),而 std::make_shared<T> 典型地仅进行一次分配(标准推荐但不要求如此,所有已知实现均如此)。
  • 若任何 std::weak_ptr 在所有共享拥有者的生存期结束后引用 std::make_shared 所创建的控制块,则 T 所占有的内存维持着,直至所有弱拥有者亦被销毁,若 sizeof(T) 较大则这可能是不想要的。
  • std::shared_ptr<T>(new T(args...)) 可能调用 T 的非公开构造函数,若在它可访问的语境中执行,而 std::make_shared 要求对被选择构造函数的公开访问。
  • 不同于 std::shared_ptr 构造函数, std::make_shared 不允许自定义删除器。
  • std::make_shared 使用 ::new ,故若用类限定的 operator new 设置了任何特殊行为,则它将异于 std::shared_ptr<T>(new T(args...)) 。
(C++20 前)
  • 如 f(std::shared_ptr<int>(new int(42)), g()) 的代码可能导致内存泄漏,若 g 在 new int(42) 后得到调用且抛出异常,而 f(std::make_shared<int>(42), g()) 是安全的,因为二个函数调用决不会穿插
(C++17 前)

构造函数以 U* 类型指针 ptr 启用 shared_from_this ,表示它确定 U 是否拥有作为 std::enable_shared_from_this 特化的无歧义且可访问 (C++17 起)基类,而若如此,则构造函数求值该语句:

if (ptr != nullptr && ptr->weak_this.expired())
  ptr->weak_this = std::shared_ptr<std::remove_cv_t<U>>(*this,
                                  const_cast<std::remove_cv_t<U>*>(ptr));

其中 weak_this 是 std::shared_from_this 的隐藏 mutable std::weak_ptr 成员。对 weak_this 成员的赋值不是原子的,且与任何到同一对象的潜在并发访问冲突。这确保将来对 shared_from_this() 调用,将与此裸指针构造函数所创建的 shared_ptr 共享所有权。

上述解释代码中,测试 ptr->weak_this.expired() 是为确保若 weak_this 指示已有占有者则重赋值它。从 C++17 起要求此测试。

示例

#include <iostream>
#include <memory>
 
void foo(const std::shared_ptr<int>& i)
{
    (*i)++;
}
 
int main()
{
    auto sp = std::make_shared<int>(12);
    foo(sp);
    std::cout << *sp << '\n';
}

输出:

13

参阅

构造新的 shared_ptr 
(公开成员函数)
创建共享指针,管理用分配器分配的新对象 
(函数模板)
(C++14)
创建管理新对象的独有指针 
(函数模板)
分配函数 
(函数)


Why make_shared

C++11 中引入了智能指针, 同时还有一个模板函数 std::make_shared 可以返回一个指定类型的 std::shared_ptr, 那与 std::shared_ptr 的构造函数相比它能给我们带来什么好处呢 ?

优点

效率更高

shared_ptr 需要维护引用计数的信息,

  • 强引用, 用来记录当前有多少个存活的 shared_ptrs 正持有该对象. 共享的对象会在最后一个强引用离开的时候销毁( 也可能释放).
  • 弱引用, 用来记录当前有多少个正在观察该对象的 weak_ptrs. 当最后一个弱引用离开的时候, 共享的内部信息控制块会被销毁和释放 (共享的对象也会被释放, 如果还没有释放的话).

如果你通过使用原始的 new 表达式分配对象, 然后传递给 shared_ptr (也就是使用 shared_ptr 的构造函数) 的话, shared_ptr 的实现没有办法选择, 而只能单独的分配控制块:

1
2
auto p = new widget();
shared_ptr sp1{ p }, sp2{ sp1 };

如果选择使用 make_shared 的话, 情况就会变成下面这样:

1
auto sp1 = make_shared(), sp2{ sp1 };

内存分配的动作, 可以一次性完成. 这减少了内存分配的次数, 而内存分配是代价很高的操作.

关于两种方式的性能测试可以看这里 Experimenting with C++ std::make_shared

异常安全

看看下面的代码:

1
2
3
4
void F(const std::shared_ptr<Lhs>& lhs, const std::shared_ptr<Rhs>& rhs) { /* ... */ }

F(std::shared_ptr<Lhs>(new Lhs("foo")),
  std::shared_ptr<Rhs>(new Rhs("bar")));

C++ 是不保证参数求值顺序, 以及内部表达式的求值顺序的, 所以可能的执行顺序如下:

  1. new Lhs(“foo”))
  2. new Rhs(“bar”))
  3. std::shared_ptr
  4. std::shared_ptr

好了, 现在我们假设在第 2 步的时候, 抛出了一个异常 (比如 out of memory, 总之, Rhs 的构造函数异常了), 那么第一步申请的 Lhs 对象内存泄露了. 这个问题的核心在于, shared_ptr 没有立即获得裸指针.

我们可以用如下方式来修复这个问题.

1
2
3
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

当然, 推荐的做法是使用 std::make_shared 来代替:

1
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

缺点

构造函数是保护或私有时,无法使用 make_shared

make_shared 虽好, 但也存在一些问题, 比如, 当我想要创建的对象没有公有的构造函数时, make_shared 就无法使用了, 当然我们可以使用一些小技巧来解决这个问题, 比如这里 How do I call ::std::make_shared on a class with only protected or private constructors?

对象的内存可能无法及时回收

make_shared 只分配一次内存, 这看起来很好. 减少了内存分配的开销. 问题来了, weak_ptr 会保持控制块(强引用, 以及弱引用的信息)的生命周期, 而因此连带着保持了对象分配的内存, 只有最后一个 weak_ptr 离开作用域时, 内存才会被释放. 原本强引用减为 0 时就可以释放的内存, 现在变为了强引用, 若引用都减为 0 时才能释放, 意外的延迟了内存释放的时间. 这对于内存要求高的场景来说, 是一个需要注意的问题. 关于这个问题可以看这里 make_shared, almost a silver bullet

参考

### C++ 中 `std::shared_ptr` 和 `std::make_shared` 的用法及区别 #### 定义与基本功能 `std::shared_ptr` 是一种智能指针,用于管理动态分配的对象。它通过引用计数机制来自动释放对象的内存[^1]。当最后一个 `std::shared_ptr` 被销毁或者重置时,所指向的对象会被删除。 `std::make_shared` 则是一个函数模板,用来创建并初始化一个由 `std::shared_ptr` 管理的对象。它的主要作用是以更高效的方式创建共享所有权的资源。 --- #### 性能差异 使用 `std::make_shared` 创建 `std::shared_ptr` 通常比直接调用 `new` 来得更加高效。这是因为 `std::make_shared` 将控制块(control block)和实际数据存储在同一块连续的内存中,减少了两次独立的内存分配操作。而手动创建 `std::shared_ptr` 需要分别分配控制块和目标对象的内存。 以下是两者的对比代码: ```cpp // 使用 new 手动创建 shared_ptr std::shared_ptr<int> ptr1(new int(42)); // 使用 make_shared 更加高效地创建 shared_ptr std::shared_ptr<int> ptr2 = std::make_shared<int>(42); ``` 上述例子展示了两种方式的区别,其中第二种方法更为推荐。 --- #### 循环依赖问题 需要注意的是,在某些情况下,如果两个或多个 `std::shared_ptr` 彼此持有对方,则可能导致循环引用问题。这种情况下,引用计数永远不会降为零,从而引发内存泄漏。为了避免这种情况的发生,可以改用 `std::weak_ptr` 来打破循环引用关系[^2]。 --- #### 编译器支持与标准兼容性 不同编译器对于 C++ 标准的支持可能存在细微差别。因此,在跨平台开发过程中应特别注意确保代码具有良好的可移植性[^4]。不过就目前而言,主流现代编译器均已全面支持 `std::shared_ptr` 和 `std::make_shared` 这些基础工具的功能实现。 --- #### 示例程序 下面给出一段完整的示例代码展示如何正确运用两者: ```cpp #include <iostream> #include <memory> class MyClass { public: MyClass(int value) : data(value) {} void display() const { std::cout << "Data: " << data << '\n'; } private: int data; }; int main() { // 方法一:传统方式构建 shared_ptr auto sp1 = std::shared_ptr<MyClass>(new MyClass(10)); // 方法二:利用 make_shared 构建 shared_ptr auto sp2 = std::make_shared<MyClass>(20); sp1->display(); sp2->display(); return 0; } ``` 以上代码片段清晰表明了两种构造形式的实际应用效果以及其简洁性和效率上的优势所在。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值