make_shared能单次内存分配完成对象和控制块的创建,提升性能与异常安全性,适用于大多数场景,但不支持自定义删除器、placement new及C++11/14中数组的创建,且在weak_ptr长期存活时可能影响内存释放。

make_shared
是C++中创建
std::shared_ptr
对象的首选方式,因为它能在一个内存分配中同时完成对象本身的构造和其管理控制块的创建,这不仅提升了性能,也大大增强了代码的异常安全性。
解决方案
在C++中,使用
make_shared
来创建
shared_ptr
对象非常直观。你只需要像调用普通构造函数一样,将目标类型和构造参数传递给
make_shared
即可。例如,如果你有一个类
MyClass
,它有一个接受
int
和
std::string
的构造函数,你可以这样创建它的
shared_ptr
:
#include #include #include class MyClass {public: int value; std::string name; MyClass(int v, const std::string& n) : value(v), name(n) { std::cout << "MyClass(" << value << ", " << name << ") constructed." << std::cout; } ~MyClass() { std::cout << "MyClass(" << value << ", " << name << ") destructed." << std::cout; } void greet() const { std::cout << "Hello from MyClass " << name << " with value " << value << "!" << std::cout; }};int main() { // 使用 make_shared 创建 shared_ptr 对象 std::shared_ptr ptr1 = std::make_shared(10, "Alpha"); ptr1->greet(); // 也可以用于无参构造函数 std::shared_ptr str_ptr = std::make_shared("Hello Shared World"); std::cout << *str_ptr << std::cout; // 甚至可以创建复杂对象,例如一个向量的shared_ptr std::shared_ptr<std::vector> vec_ptr = std::make_shared<std::vector>(5, 100); // 5个100 for (int x : *vec_ptr) { std::cout << x << " "; } std::cout << std::cout; return 0;}
这段代码清晰地展示了
make_shared
的用法。它通过模板推导来确定要创建的对象类型,并将所有后续参数完美转发给该类型的构造函数。我个人在使用C++11及更高版本时,几乎总是优先考虑
make_shared
,除非遇到它无法满足的特定场景。
make_shared
make_shared
与直接使用
new
有什么本质区别?
当我初次接触
shared_ptr
时,也曾疑惑
std::shared_ptr ptr(new T())
和
std::make_shared()
到底有什么不同。这背后的核心差异在于内存分配的次数和方式。
立即学习“C++免费学习笔记(深入)”;
直接使用
new
,比如
std::shared_ptr ptr(new MyClass(10, "Beta"))
,实际上会发生两次内存分配:
一次是
new MyClass(10, "Beta")
为
MyClass
对象本身分配内存。另一次是
std::shared_ptr
的构造函数为管理控制块(control block)分配内存。这个控制块包含了引用计数、弱引用计数以及可能的自定义删除器等信息。
而
std::make_shared(10, "Gamma")
则只进行一次内存分配。它会分配一块足够大的内存,既能容纳
MyClass
对象,也能容纳其管理控制块。然后,它会在这块内存上构造
MyClass
对象和控制块。
这种“单次分配”的优化带来了几个好处:
性能提升: 减少了一次系统调用(内存分配通常是相对耗时的操作),并且由于对象和控制块在内存中是连续的,缓存局部性更好,这在频繁创建
shared_ptr
时能带来明显的性能优势。内存碎片减少: 避免了两次独立的小块内存分配,有助于减少内存碎片。
从我的经验来看,这种优化在处理大量小对象或性能敏感的场景下尤为重要。它不仅仅是语法糖,更是对底层资源管理的一种精妙优化。
使用
make_shared
make_shared
如何避免潜在的异常安全问题?
这可能是
make_shared
最被低估的优点之一,也是我个人认为它“非用不可”的原因之一。考虑这样一个场景,如果你不使用
make_shared
,而是像下面这样在函数调用中混合了
new
和另一个可能抛出异常的函数:
void process_data(std::shared_ptr data_ptr, int priority);void log_error(const std::string& msg); // 假设这个函数可能抛出异常// 危险的写法void create_and_process_unsafe() { process_data(std::shared_ptr(new Data()), log_error("Creating Data object")); // log_error可能抛出异常}
在
create_and_process_unsafe
这个例子中,C++标准没有规定函数参数的评估顺序。编译器可能会这样做:
调用
new Data()
,分配内存并构造
Data
对象。调用
log_error("Creating Data object")
。如果
log_error
在此刻抛出了异常,那么
std::shared_ptr
的构造函数将永远不会被调用。这意味着,
new Data()
所分配的内存将无法被
shared_ptr
管理,从而导致内存泄漏!
这种隐蔽的内存泄漏非常难以调试,因为它依赖于特定的执行顺序和异常条件。
现在,我们看看使用
make_shared
的情况:
// 安全的写法void create_and_process_safe() { process_data(std::make_shared(), log_error("Creating Data object")); // log_error可能抛出异常}
在这种情况下,
std::make_shared()
是一个原子操作。它要么完全成功(创建了
Data
对象和其
shared_ptr
),要么完全失败(如果
Data
的构造函数抛出异常或内存分配失败)。无论哪种情况,
Data
对象要么被正确管理,要么根本就没有被创建。
shared_ptr
的创建是完整的,不会出现裸指针悬空的情况。因此,即使
log_error
抛出异常,也不会导致
Data
对象的内存泄漏。这种异常安全性的保证,在我看来,是
make_shared
最重要的价值之一,它能帮我们避免很多潜在的程序崩溃或资源耗尽问题。
make_shared
make_shared
在哪些场景下可能不适用或需要注意?
尽管
make_shared
有诸多优点,但它并非万能药。有些特定场景下,我们可能需要退而求其次,或者采用其他策略:
自定义删除器(Custom Deleters): 如果你需要为
shared_ptr
指定一个自定义的删除器(例如,释放C风格数组、关闭文件句柄等),
make_shared
的直接构造函数并不支持传递删除器。在这种情况下,你必须使用
shared_ptr
的构造函数,像这样:
std::shared_ptr file_ptr(fopen("test.txt", "w"), [](FILE* f){ if (f) { std::cout << "Closing file..." << std::cout; fclose(f); }});
这里,
fopen
返回的裸指针被直接传递给
shared_ptr
构造函数,并附带了lambda表达式作为删除器。
Placement New 或预分配内存: 如果你的对象需要在已有的内存块上进行构造(例如,为了与C API交互或进行某些低级内存优化),
make_shared
就无法满足需求了,因为它总是自己分配内存。你将需要手动管理内存和对象的生命周期。
数组(C++11/14): 在C++11和C++14中,
make_shared
不直接支持创建动态数组的
shared_ptr
。你不能写
std::make_shared(10)
。你需要使用
std::shared_ptr arr_ptr(new int[10])
。不过,在C++17及更高版本中,
make_shared
已经扩展了对数组的支持,你可以直接使用
std::make_shared(10)
或
std::make_shared()
。这是一个不错的语言进步,解决了早期版本的一个小痛点。
当
weak_ptr
存活时间远超
shared_ptr
,且对象本身很大时: 这是一个比较高级且微妙的内存优化考量。由于
make_shared
将对象和控制块放在同一块内存中,即使所有
shared_ptr
都已经销毁,只要还有
std::weak_ptr
引用着这个控制块(即弱引用计数不为零),那么这整块内存(包括已经没有用的对象数据部分)就无法被释放。如果你的对象非常大,并且
weak_ptr
的生命周期显著长于
shared_ptr
,这可能会导致不必要的内存占用。在这种特定且罕见的场景下,使用
std::shared_ptr ptr(new T())
(两次分配)可能会更优,因为当所有
shared_ptr
销毁后,对象本身的内存可以立即释放,只留下控制块的内存直到所有
weak_ptr
销毁。但这通常只在极端内存敏感的系统中才需要考虑。
总的来说,对于大多数日常编程任务,
make_shared
是创建
shared_ptr
的最佳选择。只有当遇到上述这些特定场景时,我们才需要考虑其他替代方案。了解这些限制,能帮助我们更全面、更合理地运用C++的智能指针。
以上就是C++如何使用make_shared创建shared_ptr对象的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1474586.html
微信扫一扫
支付宝扫一扫