【问题标题】:Moving a shared_ptr<StructA> into a shared_ptr<variant<StructA, StructB>>将 shared_ptr<StructA> 移动到 shared_ptr<variant<StructA, StructB>>
【发布时间】:2020-07-13 15:34:34
【问题描述】:

我试图避免一组普通旧数据结构的相当复杂的继承链,但我需要将它们全部存储在一个向量中 + 与调用者共享所有权。

Struct A {};
Struct B {};

using TInstruction = std::variant<A, B>;

struct holder {
  std::vector<std::shared_ptr<TInstruction>> items;
};

static holder h;

// Every individual TInstruction gets its own add function, for validation purposes
void add(std::shared_ptr<A> a) {
  // Somehow add this to h, while still sharing ownership with the caller
  // h.emplace_back(???)
}


int main() {
  holder h;

  auto a = std::make_unique<A>();
  auto b = std::make_unique<B>();

  add(a);
  // add(b) // not implemented
}

我对最初的想法进行了以下更改,取得了中等(但令人讨厌的糟糕)成功:

  1. 在变体中使用共享指针,即using TInstruction = std::variant&lt;std::shared_ptr&lt;A&gt;, std::shared_ptr&lt;B&gt;&gt;
  2. 接受add() 中的std::weak_ptr 并使用.lock() 将其转换为std::shared_ptr

我不介意 #2(在我看来,这可能是正确的做法),但是将 shared_ptr 保留在变体的“外部”内部会导致一些非常冗长的代码和模式匹配.

有可能做到这一点吗?我本质上是想改变共享指针的类型,但仍然表达共享所有权的想法。

【问题讨论】:

  • 也许您可以使用“别名构造函数”(cppreference 上的#8)。尽管它给出的示例与您想要的相反。如果您已经有一个指向变体的共享指针并且想要创建一个指向具有相同所有权的内部对象的共享指针,那么使用它会很简单。但反过来可能就不行了。
  • 实际上,您打算如何将对象放入变体中?变体要么拥有它自己的副本,要么将对象移入其中。无论哪种情况,为什么它仍然需要与调用者共享所有权?如果您真的想在变体中拥有相同的对象,那么在我看来选项 1 是您唯一的选择。
  • 共享指针的变体绝对是解决此问题的“正确”方式。它是冗长的怎么办?可能有办法减少这种情况。
  • @Kevin & @HTNW 谢谢你,看起来这个问题有点傻,我明白为什么会这样:) API 打算让用户只用一堆说.add(&lt;reference_to_shared_ptr&gt;)不同的类型,使零件或多或少地得到解决。我必须在代码的几个部分中对这些类型进行“模式匹配”,这是每次拼出variant.get&lt;shared_ptr&lt;StructType&gt;&gt;(var) 变得相当冗长的地方。但我会接受这个打击。我知道这个问题很奇怪,但我不是专家,所以我想在这里联系:)

标签: c++ c++14 c++17


【解决方案1】:

除了使用variant,您还可以利用shared_ptr&lt;void&gt; 对任何东西持有shared_ptr,只要您跟踪它持有自己的类型,如下所示:

// Generic typelist
template <typename...>
struct Types;

// Find the 0-based index of type T in Types<...>
template <typename, typename>
struct Index;

// T is the first type in Types<...>
template <typename T, typename... Us> 
struct Index<T, Types<T, Us...>> : std::integral_constant<int, 0> {}; 

// T is not the first type in Types<...>
template <typename T, typename U, typename... Us> 
struct Index<T, Types<U, Us...>>
    : std::integral_constant<int, 1 + Index<T, Types<Us...>>()> {}; 

template <typename... Ts> 
struct SharedPtrVariant {
    template <typename T>
    explicit SharedPtrVariant(std::shared_ptr<T> p)
        : sp(std::move(p)), index(Index<T, Types<Ts...>>()) {}

    template <typename T>
    std::shared_ptr<T> get() const {
        return std::static_pointer_cast<T>(
            Index<T, Types<Ts...>>() == index ? sp : nullptr);
    }   

   private:
    std::shared_ptr<void> sp; 
    int index;
};

【讨论】:

    【解决方案2】:

    根据 cmets,这是一个有点愚蠢的问题,因为变体必须是对象的所有者,最终类型才能为 shared_pointer&lt;variant&gt;。 Nevin 的建议可行,但使用 void 类型,它有其自身的问题(也许可以使用 any 类型?那不会更好。)

    最后,我希望尽可能多地保持类型安全并明确所有权模型,因此我将满足于共享指针的变体。

    【讨论】:

    • shared_ptr&lt;void&gt;有什么问题?它仍然拥有该对象并知道如何引用它,如果它是最后一个引用并且超出范围,则正确删除它,是reset(),等等。
    • @Nevin 我可能错了,但我需要知道变体中的确切类型。我假设 shared_ptr 是相当不安全的类型(即使可以用它编写潜在安全的代码。)
    • 它和std::any 一样类型安全,因为它们都在幕后使用了类型擦除。您必须跟踪确切的类型才能将其取出,您可以像variant 那样做;即,类型列表的索引。当引用计数变为 0 时,您不需要确切的类型来正确销毁它,因为即使 shared_ptr&lt;void&gt;shared_ptr&lt;T&gt; 都指向同一个对象,shared_ptr 也会为您正确地销毁它。
    猜你喜欢
    • 2017-06-27
    • 1970-01-01
    • 2010-12-27
    • 2021-09-24
    • 2016-11-19
    • 1970-01-01
    • 2014-04-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多