我什么时候想从原始指针构造共享指针
When would I want to construct a shared pointer from a raw pointer
感谢 std::make_shared
,我想知道 std::shared_ptr
的构造函数是否有任何值,除了与遗留/库代码接口时,例如存储工厂的输出时。
- 还有其他合法的用例吗?
- 避免使用该构造函数是否合理?
- 甚至将代码检查到位,以便在使用时警告程序员?
- 是否应将相同的准则(无论它们是什么)应用于
shared_ptr<T>::reset(T*)
?
关于代码检查:
我知道与遗留/库代码的接口很常见,因此自动代码检查可能会有问题,但在我目前遇到的大多数情况下,我宁愿使用 unique_ptr
无论如何我也不是在谈论在 -Wall
处弹出的编译器警告,而是关于代码审查期间静态代码分析的规则。
我的动力:
说 "Don't use std::shared_ptr<T>(new T(...))
, always prefer std::make_shared<T>(...)
" 相对容易(我认为这是正确的建议?)。但我想知道这是否不是一般的设计味道,是否必须从原始指针创建 shared_ptr
,甚至 - 或者特别是 - 如果对象不是通过 new
创建的,因为首先应该将对象创建为 "shared" 或 "unique" 对象。
首先想到的用例是删除器不是默认的 delete
。
例如在 windows 环境中,有时必须使用 COM 对象,这些对象的释放必须通过 Release
在对象本身上完成。 ATL当然可以用,但不是每个人都想用
struct ReleaseCom {
template <class T>
void operator() (T* p) const
{
p->Release();
}
};
IComInterface* p = // co created or returned as a result
std::share_ptr<IComInterface> sp(p, ReleaseCom());
一种更不常见的情况 - 但仍然有效 - 是在 dll、OS 或库中自定义分配对象(句柄甚至原始内存)并具有必须调用的自己的关联清理函数(可能会或可能不会依次调用 delete
)。如果涉及内存分配,std::allocate_shared
提供对要使用的分配器的更多增强控制,而无需暴露原始指针。
我个人的感觉是,鉴于std::make_shared
和std::allocate_shared
,从原始指针构造shared_ptr
的要求越来越少了。即使是上述情况,也可以包装到实用程序分配和管理功能中,从主要业务逻辑代码中删除。
• Are there other legitimate use-cases?
是:存在资源未映射到 new/delete:
的情况
handle_type APIXCreateHandle(); // third party lib
void APIXDestroyHandle(handle_type h); // third party lib
在这种情况下,您将希望直接使用构造函数。
• Is it reasonable advice to avoid that constructor?
当功能与 make_shared 重叠时,是的。
• Should the same guidelines (whatever they are) apply to shared_ptr::reset(T*)?
我认为他们不应该。如果您确实需要,可以将 reset 调用替换为具有 std::make_shared 调用结果的赋值。不过,我更愿意看到重置调用 - 它的意图会更明确。
Regarding the code checks: I know that interfacing with legacy / library code is quite common
考虑将第三方库包装到一个接口中,以确保返回值为 unique_ptr-wrapped。这将为您提供集中点和其他 convenience/safety 优化的机会。
It is relatively easy to say [...] (Which I believe is correct advise?). But I'm wondering if it isn't a design smell in general
使用它不是设计气味;仅在 std::make_shared/std::make_unique 工作正常时才使用它.
编辑:解决问题的要点:您可能无法为此添加静态分析规则,除非您还添加 convention/exception 列出它(即 "except for custom deleters and third party lib API adaptation layers, make_shared and make_unique should always be used")。您的某些文件可能会跳过这样的规则。
当你直接使用构造函数时,将它们放在一个专门用于此目的的函数中可能是个好主意(类似于 make_unique 和 make_shared 所做的):
namespace api_x
{
std::shared_ptr<handle_type> make_handle(); // calls APIXCreateHandle internally
// also calls the constructor
// to std::shared_ptr
}
Scott Meyers 在 Effective Modern C++
中列出了一些例外情况
第一个已经提到了。如果您需要提供自定义删除器,则不能使用 make_shared
.
第二个是如果你在一个有内存问题的系统上并且你正在分配一个非常大的对象然后使用 make_shared
为对象和 控制块分配一个块 包括 weak 引用计数。如果有任何 weak_ptr
s 指向该对象,则无法释放内存。另一方面,如果您没有使用 make_shared
,那么一旦最后一个 shared_ptr
被删除,那么可以释放非常大的对象的内存。
尽可能使用 std::make_shared
。
您可能无法逃脱的原因有两个:
您想为您的对象使用自定义删除器。如果您与不使用 RAII 的代码交互,则可能会出现这种情况。其他答案提供了更多详细信息。
您有一个要调用的自定义新接线员。 std::make_shared
不能为你调用它,因为它的全部意义在于分配内存 一次(尽管标准不要求)。见 http://ideone.com/HjmFl1.
std::make_shared
背后的原因不是您避免了 new
关键字,而是您避免了内存分配。共享指针需要建立一些控制数据结构。所以当设置一个没有make_shared
的shared_ptr
时,你必须做两次分配:一次分配给对象,另一次分配给控制结构。 make_shared
(通常)只分配一个(更大的)内存块并就地构造包含的对象和控制结构。 make_shared
背后的原因是(最初是)它在大多数情况下更有效,而不是语法更漂亮。这也是C++11中没有std::make_unique
的原因。 (正如 ChrisDrew 所指出的,我建议只为 symmetry/prettier 语法添加 std::make_unique
。它可以帮助以更紧凑的方式编写异常安全代码,请参阅 Herb Sutter's GotW #102 or this question。)
除了其他答案,如果构造函数是私有的,例如来自工厂函数,则不能使用 make_shared
。
class C
{
public:
static std::shared_ptr<C> create()
{
// fails
// return std::make_shared<C>();
return std::shared_ptr<C>(new C);
}
private:
C();
};
很多答案至少提供了一个独特的方面,所以我决定做一个总结性的回答。感谢@Niall、@Chris Drew、@utnapistim、@isanae、@Markus Mayr 和(通过代理)Scott Meyers。
您可能不想/可能无法使用 make_shared
的原因是:
- 您必须使用自定义删除器 and/or 甚至自定义 allocator/new 运算符。
这可能是最常见的原因,尤其是在与 3rd 方库交互时。 std::allocate_shared
在某些情况下可能会有所帮助。
- 如果内存是一个问题并且您经常有比 object 寿命更长的弱指针。
由于托管 object 是在与控制块相同的内存块上创建的,因此在最后一个弱指针也被销毁之前无法释放内存。
- 如果您有私有构造函数,例如只有一个 public 工厂函数。
在这种情况下,让 make_shared
成为朋友不是一个可行的解决方案,因为实际的构造可能发生在某些助手 function/class. 中
关于代码检查,上面列出的例外情况可能太多,无法自动检查 "Use make_shared
whenever possible" 指南,也可能会调用违反该指南的行为指导设计气味本身。
作为 horizon 上的一个亮点,即使库需要自定义分配器和释放器函数而我们不能/不想使用 std::allocate_shared
,我们也可以制作自己的make shared 的版本至少封装了分配和删除调用并提高了异常安全性(尽管它很可能不提供单一分配优势)。
感谢 std::make_shared
,我想知道 std::shared_ptr
的构造函数是否有任何值,除了与遗留/库代码接口时,例如存储工厂的输出时。
- 还有其他合法的用例吗?
- 避免使用该构造函数是否合理?
- 甚至将代码检查到位,以便在使用时警告程序员?
- 是否应将相同的准则(无论它们是什么)应用于
shared_ptr<T>::reset(T*)
?
关于代码检查:
我知道与遗留/库代码的接口很常见,因此自动代码检查可能会有问题,但在我目前遇到的大多数情况下,我宁愿使用 unique_ptr
无论如何我也不是在谈论在 -Wall
处弹出的编译器警告,而是关于代码审查期间静态代码分析的规则。
我的动力:
说 "Don't use std::shared_ptr<T>(new T(...))
, always prefer std::make_shared<T>(...)
" 相对容易(我认为这是正确的建议?)。但我想知道这是否不是一般的设计味道,是否必须从原始指针创建 shared_ptr
,甚至 - 或者特别是 - 如果对象不是通过 new
创建的,因为首先应该将对象创建为 "shared" 或 "unique" 对象。
首先想到的用例是删除器不是默认的 delete
。
例如在 windows 环境中,有时必须使用 COM 对象,这些对象的释放必须通过 Release
在对象本身上完成。 ATL当然可以用,但不是每个人都想用
struct ReleaseCom {
template <class T>
void operator() (T* p) const
{
p->Release();
}
};
IComInterface* p = // co created or returned as a result
std::share_ptr<IComInterface> sp(p, ReleaseCom());
一种更不常见的情况 - 但仍然有效 - 是在 dll、OS 或库中自定义分配对象(句柄甚至原始内存)并具有必须调用的自己的关联清理函数(可能会或可能不会依次调用 delete
)。如果涉及内存分配,std::allocate_shared
提供对要使用的分配器的更多增强控制,而无需暴露原始指针。
我个人的感觉是,鉴于std::make_shared
和std::allocate_shared
,从原始指针构造shared_ptr
的要求越来越少了。即使是上述情况,也可以包装到实用程序分配和管理功能中,从主要业务逻辑代码中删除。
• Are there other legitimate use-cases?
是:存在资源未映射到 new/delete:
的情况handle_type APIXCreateHandle(); // third party lib
void APIXDestroyHandle(handle_type h); // third party lib
在这种情况下,您将希望直接使用构造函数。
• Is it reasonable advice to avoid that constructor?
当功能与 make_shared 重叠时,是的。
• Should the same guidelines (whatever they are) apply to shared_ptr::reset(T*)?
我认为他们不应该。如果您确实需要,可以将 reset 调用替换为具有 std::make_shared 调用结果的赋值。不过,我更愿意看到重置调用 - 它的意图会更明确。
Regarding the code checks: I know that interfacing with legacy / library code is quite common
考虑将第三方库包装到一个接口中,以确保返回值为 unique_ptr-wrapped。这将为您提供集中点和其他 convenience/safety 优化的机会。
It is relatively easy to say [...] (Which I believe is correct advise?). But I'm wondering if it isn't a design smell in general
使用它不是设计气味;仅在 std::make_shared/std::make_unique 工作正常时才使用它.
编辑:解决问题的要点:您可能无法为此添加静态分析规则,除非您还添加 convention/exception 列出它(即 "except for custom deleters and third party lib API adaptation layers, make_shared and make_unique should always be used")。您的某些文件可能会跳过这样的规则。
当你直接使用构造函数时,将它们放在一个专门用于此目的的函数中可能是个好主意(类似于 make_unique 和 make_shared 所做的):
namespace api_x
{
std::shared_ptr<handle_type> make_handle(); // calls APIXCreateHandle internally
// also calls the constructor
// to std::shared_ptr
}
Scott Meyers 在 Effective Modern C++
中列出了一些例外情况第一个已经提到了。如果您需要提供自定义删除器,则不能使用 make_shared
.
第二个是如果你在一个有内存问题的系统上并且你正在分配一个非常大的对象然后使用 make_shared
为对象和 控制块分配一个块 包括 weak 引用计数。如果有任何 weak_ptr
s 指向该对象,则无法释放内存。另一方面,如果您没有使用 make_shared
,那么一旦最后一个 shared_ptr
被删除,那么可以释放非常大的对象的内存。
尽可能使用 std::make_shared
。
您可能无法逃脱的原因有两个:
您想为您的对象使用自定义删除器。如果您与不使用 RAII 的代码交互,则可能会出现这种情况。其他答案提供了更多详细信息。
您有一个要调用的自定义新接线员。
std::make_shared
不能为你调用它,因为它的全部意义在于分配内存 一次(尽管标准不要求)。见 http://ideone.com/HjmFl1.
std::make_shared
背后的原因不是您避免了 new
关键字,而是您避免了内存分配。共享指针需要建立一些控制数据结构。所以当设置一个没有make_shared
的shared_ptr
时,你必须做两次分配:一次分配给对象,另一次分配给控制结构。 make_shared
(通常)只分配一个(更大的)内存块并就地构造包含的对象和控制结构。 make_shared
背后的原因是(最初是)它在大多数情况下更有效,而不是语法更漂亮。这也是C++11中没有std::make_unique
的原因。 (正如 ChrisDrew 所指出的,我建议只为 symmetry/prettier 语法添加 std::make_unique
。它可以帮助以更紧凑的方式编写异常安全代码,请参阅 Herb Sutter's GotW #102 or this question。)
除了其他答案,如果构造函数是私有的,例如来自工厂函数,则不能使用 make_shared
。
class C
{
public:
static std::shared_ptr<C> create()
{
// fails
// return std::make_shared<C>();
return std::shared_ptr<C>(new C);
}
private:
C();
};
很多答案至少提供了一个独特的方面,所以我决定做一个总结性的回答。感谢@Niall、@Chris Drew、@utnapistim、@isanae、@Markus Mayr 和(通过代理)Scott Meyers。
您可能不想/可能无法使用 make_shared
的原因是:
- 您必须使用自定义删除器 and/or 甚至自定义 allocator/new 运算符。
这可能是最常见的原因,尤其是在与 3rd 方库交互时。std::allocate_shared
在某些情况下可能会有所帮助。 - 如果内存是一个问题并且您经常有比 object 寿命更长的弱指针。
由于托管 object 是在与控制块相同的内存块上创建的,因此在最后一个弱指针也被销毁之前无法释放内存。 - 如果您有私有构造函数,例如只有一个 public 工厂函数。
在这种情况下,让make_shared
成为朋友不是一个可行的解决方案,因为实际的构造可能发生在某些助手 function/class. 中
关于代码检查,上面列出的例外情况可能太多,无法自动检查 "Use make_shared
whenever possible" 指南,也可能会调用违反该指南的行为指导设计气味本身。
作为 horizon 上的一个亮点,即使库需要自定义分配器和释放器函数而我们不能/不想使用 std::allocate_shared
,我们也可以制作自己的make shared 的版本至少封装了分配和删除调用并提高了异常安全性(尽管它很可能不提供单一分配优势)。