【问题标题】:C++11 make_shared instancingC++11 make_shared 实例化
【发布时间】:2016-06-27 11:58:21
【问题描述】:

抱歉,问题很长,但需要一些上下文。我有一些代码似乎对我正在从事的项目很有用:

class Foo
{
public:
    Foo( int bar = 1 );
    ~Foo();
    typedef std::shared_ptr< Foo > pointer_type;
    static pointer_type make( int bar = 1 )
    {
        return std::make_shared< Foo >( bar );
    }

...
}

如您所见,它提供了一种将任何类构造为 PointerType 的简单方法,该类型将 shared_ptr 封装到该类型:

auto oneFoo = Foo::make( 2 );

因此,您无需在整个代码库中都引用 make_shared 和 shared_ptr 即可获得 shared_ptr 的优势。

在类中封装智能指针类型有几个优点:

  1. 它可以让您控制指针类型的可复制性和可移动性。
  2. 它对调用者隐藏了 shared_ptr 的详细信息,因此可以在 Instance() 调用中放置非平凡的对象构造,例如那些引发异常的构造。
  3. 在处理使用多个智能指针实现的项目时,您可以更改基础智能指针类型。您可以切换到 unique_ptr 甚至切换到特定类的原始指针,并且调用代码将保持不变。
  4. 它将有关(智能)指针构造和别名的详细信息集中在最了解如何操作的类中。
  5. 它可以让您决定哪些类可以使用智能指针以及哪些类必须在堆栈上构建。 PointerType 字段的存在向调用者提供了关于可以创建哪些类型的指针与类对应的提示。如果没有为一个类定义 PointerType,这将表明不能创建指向该类的指针;因此,必须在堆栈上以 RAII 样式创建该特定类。

但是,如果不直接键入必需的 typedef 和静态 PointerType Instance() 函数,我看不到将这段代码应用到我项目中的所有类的明显方法。我怀疑应该有一些一致的、C++11 标准的、跨平台的方式来使用基于策略的模板来执行此操作,但是一些实验并没有找到一种明显的方式来将它简单地应用于一个类中的一堆类在所有现代 C++ 编译器上都能干净地编译的方式。

你能想出一种优雅的方式来将这些概念添加到一堆类中,而不需要大量的剪切和粘贴吗?一个理想的解决方案将在概念上限制可以为哪些类型的类创建哪些类型的指针(一个类使用 shared_ptr 而另一个使用原始指针),并且它还将通过其自己的首选方法处理任何受支持类型的实例化。这样的解决方案甚至可以通过在编译时适当地在非标准和标准智能指针类型和哑指针类型之间失败来处理和/或限制强制。

【问题讨论】:

  • TBH 我不认为你正在做的工作比直接使用std::make_shared 少。
  • 如果你想要指向 this 的弱指针,你可能需要考虑从 std::enable_shared_from_this 派生。
  • 除非您使用shared_from_this,否则工厂实际上应该返回std::unique_ptr,因为返回的指针是指向特定对象的唯一指针。从unique_ptr 得到shared_ptr 很容易,而反过来则完全不可能。
  • @Ben,如果我可以点赞评论,我会的。不要混合指向同一个对象的唯一指针和共享指针。工厂可以返回一个 shared_ptr。共享意味着多于一件事物可能指向对象,而不是多于一件事物必须指向它。
  • @RobL 我认为他的意思是std::unique_ptr 可以移动到std::shared_ptr,从而转移所有权。由于显而易见的原因,反过来是不可能的。

标签: c++ templates c++11 shared-ptr smart-pointers


【解决方案1】:

一种方法是使用curiously recurring template pattern

template<typename T>
struct shared_factory
{
    using pointer_type = std::shared_ptr<T>;

    template<typename... Args>
    static pointer_type make(Args&&... args)
    {
        return std::make_shared<T>(std::forward<Args>(args)...);
    }
};

struct foo : public shared_factory<foo>
{
    foo(char const*, int) {}
};

我相信这会给你想要的。

foo::pointer_type f = foo::make("hello, world", 42);

但是...

我不建议使用这种方法。试图规定一个类型的用户如何实例化该类型是不必要的限制。如果他们需要std::shared_ptr,他们可以创建一个。如果他们需要std::unique_ptr,他们可以创建一个。如果他们想在堆栈上创建一个对象,他们可以。我认为强制用户对象的创建和管理方式没有任何好处。

解决您的问题:

  1. 它可以让您控制指针类型的可复制性和可移动性。

这有什么好处?

  1. 它对调用者隐藏了 shared_ptr 的详细信息,因此可以在 Instance() 调用中放置非平凡的对象构造,例如那些引发异常的构造。

我不确定你在这里的意思。希望不是您可以捕获异常并返回nullptr。那将是 Java 级的糟糕。

  1. 在处理使用多个智能指针实现的项目时,您可以更改基础智能指针类型。您可以切换到 unique_ptr 甚至切换到特定类的原始指针,并且调用代码将保持不变。

如果您正在使用多种智能指针,也许让用户为给定的情况选择合适的类型会更好。此外,我认为具有相同的调用代码但返回不同类型的句柄可能会令人困惑。

  1. 它将有关(智能)指针构造和别名的详细信息集中在最了解如何操作的类中。

一个类在什么意义上“最了解”如何进行指针构造和别名?

  1. 它可以让您决定哪些类可以使用智能指针以及哪些类必须在堆栈上构建。 PointerType 字段的存在向调用者提供了关于可以创建哪些类型的指针与类对应的提示。如果没有为一个类定义 PointerType,这表明不能创建指向该类的指针;因此,必须在堆栈上以 RAII 样式创建该特定类。

再一次,我根本不同意某种类型的对象必须以某种方式创建和管理的想法。这也是singleton pattern 如此阴险的原因之一。

【讨论】:

  • 在本例中,您将返回 std::shared_ptr,但将来您可以将该 typedef 更改为另一个智能指针,然后更改函数返回的内容,对吗?跨度>
  • @mock_blatt 是的,就是这样。
  • @johnwbyrd 完全修改了我的答案。我认为它为您提供了解决方案。然而,在道德上我有义务提出警告,以免我的建议被用于作恶。
  • 你能举一个你想要的例子吗?限制或鼓励指针类型之间的转换是什么意思?听起来您将需要编写自己的智能指针,或者至少是标准智能指针的包装器(可能最好:编写正确的智能指针很困难)。您能否详细说明shared_factory 的含义,知道可以创建哪些指针?悄悄换掉是什么意思?
  • 我只想删除你答案的上半部分。从“但是...”开始,全是正确的,A+,会再读一遍,投赞成票。
【解决方案2】:

我不建议添加这些静态函数。除了其他缺点之外,当有多个构造函数时,它们的创建和维护确实变得非常繁琐。在这种情况下, auto 以及类外的 typedef 都可以提供帮助。另外,您可以使用 std 命名空间(但请不要在标题中):

class Foo
{
public:
    Foo();
    ~Foo();
    Foo( int bar = 1 );

...
}

typedef std::shared_ptr<Foo> FooPtr;

在 C++ 文件中:

using namespace std;
auto oneFoo = make_shared<Foo>( 2 );
FooPtr anotherFoo = make_shared<Foo>( 2 );

我想你会发现这在打字时不会太繁琐。当然,这都是风格问题。

【讨论】:

  • 正如我在问题前面提到的,这种方法意味着您将 shared_ptr 和 make_shared 引用分散在整个代码库中。不幸的是,这会将您锁定在 C++11 shared_ptr 类型中。你能想出一种方法将这些细节隐藏在类本身中,这样外部代码就不必跟踪指针实现细节了吗?
  • 我想如果这是您的目标,您只需编写自己的智能指针实现并使用它。然后您可以根据需要更改内部结构。与您的简单示例等效的是您自己的智能指针模板,它只是吐出一个共享指针,但如果需要,您可以更改它。使用您当前的方法,想象一下如果您决定在许多项目中更改跨多个类的实现,那将是一场灾难。
  • @johnwbyrd 如果您希望以后灵活地使用不同的智能指针,您能否使用类型别名并将make_shared 包装在您自己的函数中?
  • 这当然是可能的,但我建议应该由类本身来决定该类的智能指针应该是什么样子。在我当前的用例中,我有一个使用自己的自定义智能指针的旧代码库,并且我有一些新的 C++11 代码需要 shared_ptrs 用于所有内容。感谢你们两位继续思考这个问题。
【解决方案3】:

为了使指针更易于配置,这是对 Joseph 答案的改进:

#include <memory>

template <typename T, template <typename...> class PtrT = std::shared_ptr>
struct ptr_factory {
    using pointer_type = PtrT<T>;

    template <typename... Args>
    static pointer_type make(Args&&... args) {
        return pointer_type{new T{args...}};
    }
};

template <typename T>
struct ptr_factory<T, std::shared_ptr> {
    using pointer_type = std::shared_ptr<T>;

    template <typename... Args>
    static pointer_type make(Args&&... args) {
        return std::make_shared<T>(args...);
    }
};

struct foo : public ptr_factory<foo> {
        foo(char const*, int) {}
};

struct bar : public ptr_factory<bar, std::unique_ptr> {
    bar(char const*, int) {}
};

ptr_factory 默认使用std::shared_ptr,但可以配置为使用不同的智能指针模板,这要归功于模板模板参数,如struct bar 所示。

【讨论】:

  • 我怀疑使用自定义智能指针的好处会胜过使用 std::make_shared 的好处。
  • @Slava:没有引用计数的指针肯定会胜过make_shared。分配组合减少了引用计数元数据的开销,但不能消除对它的重复访问——你的性能仍然要为计数逻辑买单。
  • @BenVoigt 如果我不需要共享所有权,它将表现出色。如果我这样做了,那么我将失去std::unique_ptr 的更高效率和std::make_shared 更好的内存分配
  • @Slava 我认为std::make_shared 只是语法糖,显式构造它们没有任何缺点。
  • @kamikaze 你想错了“与那个表达式相反,std::make_shared 通常为 T 对象和 std::shared_ptr 的控制块分配内存,只分配一次内存(这是标准中的非约束性要求),其中 std::shared_ptr(new T(args...)) 执行至少两次内存分配。"
猜你喜欢
  • 2012-10-09
  • 1970-01-01
  • 2014-09-23
  • 2012-10-29
  • 2017-06-15
  • 1970-01-01
  • 1970-01-01
  • 2019-10-17
  • 1970-01-01
相关资源
最近更新 更多