【问题标题】:Non-copying std::shared_ptr<boost::any>?非复制 std::shared_ptr<boost::any>?
【发布时间】:2017-04-24 18:09:42
【问题描述】:

我以“共享所有权”存储“不同类型的实例”。这就是我目前所做的:

class Destructible {
public:
    virtual ~Destructible() = default;
};

// UGLY
class MyType1 : public Destructible { ... };
class MyTypeN : public Destructible { ... };

class Storage {
    std::vector<std::shared_ptr<Destructible>> objects_;
    ...
}

我很想切换到boost::any,删除所有这些一致性并获得存储真正任何类型的实例的能力。我也喜欢boost::any接口和boost::any_cast

但我的类型不满足ValueType 的要求,它们不可复制。这个问题的最佳(最好是现有的)解决方案是什么? shared_any_ptr 之类的东西,它在创建时捕获析构函数,具有类型擦除、引用计数器,并且可以执行 any_cast

编辑:boost::any 允许使用移动创建,但我宁愿不移动和使用指针。 Edit2:我也广泛使用make_shared,所以make_shared_any_ptr 会派上用场。

【问题讨论】:

  • 写你自己的any?这是所有类型擦除中最简单的一种,所以这是一个很好的练习。
  • @Barry 只是想知道在 Boost 或其他地方是否有解决该问题的现有解决方案。
  • 你可以使用boost::variant,它也更安全。
  • @IgorR。在我的情况下,我需要在Storage 中键入擦除。我什至想删除存储TheirTypes 的基类限制;)
  • 你能解释一下为什么 shared_ptr 和 any 的组合不能解决你的问题吗?

标签: c++ c++11 boost c++14 shared-ptr


【解决方案1】:

这对于共享指针来说并不棘手。我们甚至可以避免多次分配。

struct any_block {
  any_block(any_block const&)=delete;
  template<class T>
  T* try_get() {
    if (!info || !ptr) return nullptr;
    if (std::type_index(typeid(T)) != std::type_index(*info)) return nullptr;
    return static_cast<T*>(ptr);
  }
  template<class T>
  T const* try_get() const {
    if (!info || !ptr) return nullptr;
    if (std::type_index(typeid(T)) != std::type_index(*info)) return nullptr;
    return static_cast<T const*>(ptr);
  }
  ~any_block() {
    cleanup();
  }
protected:
  void cleanup(){
    if (dtor) dtor(this);
    dtor=0;
  }
  any_block() {}
  std::type_info const* info = nullptr;
  void* ptr = nullptr;
  void(*dtor)(any_block*) = nullptr;
};
template<class T>
struct any_block_made:any_block {
  std::aligned_storage_t<sizeof(T), alignof(T)> data;
  any_block_made() {}
  ~any_block_made() {}
  T* get_unsafe() {
    return static_cast<T*>((void*)&data);
  }
  template<class...Args>
  void emplace(Args&&...args) {
    ptr = ::new((void*)get_unsafe()) T(std::forward<Args>(args)...);
    info = &typeid(T);
    dtor = [](any_block* self){
      static_cast<any_block_made<T>*>(self)->get_unsafe()->~T();
    };
  }
};
template<class D>
struct any_block_dtor:any_block {
  std::aligned_storage_t<sizeof(D), alignof(D)> dtor_data;
  any_block_dtor() {}
  ~any_block_dtor() {
    cleanup();
    if (info) dtor_unsafe()->~D();
  }
  D* dtor_unsafe() {
    return static_cast<D*>((void*)&dtor_data);
  }
  template<class T, class D0>
  void init(T* t, D0&& d) {
    ::new( (void*)dtor_unsafe() ) D(std::forward<D0>(d));
    info = &typeid(T);
    ptr = t;
    dtor = [](any_block* s) {
      auto* self = static_cast<any_block_dtor<D>*>(s);
      (*self->dtor_unsafe())( static_cast<T*>(self->ptr) );
    };
  }
};

using any_ptr = std::shared_ptr<any_block>;
template<class T, class...Args>
any_ptr
make_any_ptr(Args&&...args) {
  auto r = std::make_shared<any_block_made<T>>();
  if (!r) return nullptr;
  r->emplace(std::forward<Args>(args)...);
  return r;
}
template<class T, class D=std::default_delete<T>>
any_ptr wrap_any_ptr( T* t, D&& d = {} ) {
    auto r = std::make_shared<any_block_dtor<std::decay_t<D>>>();
    if (!r) return nullptr;
    r->init( t, std::forward<D>(d) );
    return r;
}

您必须实现any_cast,但使用try_get&lt;T&gt; 应该很容易。

可能有一些像const T这样的极端情况,上面没有处理。

template<class T>
std::shared_ptr<T>
crystalize_any_ptr( any_ptr ptr ) {
  if (!ptr) return nullptr;
  T* pt = ptr->try_get<T>();
  if (!pt) return nullptr;
  return {pt, ptr}; // aliasing constructor
}

如果类型匹配而不复制任何内容,这可以让您获取 any_ptr 并将其转换为 shared_ptr&lt;T&gt;

live example.

您会注意到any_block_madeany_block_dtor 有多么相似。我相信这就是为什么 std 库中至少有一个主要的 shared_ptr 将删除器所在的位置重用于 make_shared 本身。

我可能会做类似的事情,并在这里减少二进制大小。此外,any_block_madeany_block_dtorT/D 参数实际上只是我们使用的内存块有多大和对齐,以及我在dtor 中存储的确切类型擦除助手父级中的指针。带有 COMDAT 折叠(MSVC 或 GOLD)的编译器/链接器可能会消除这里的二进制膨胀,但我可以自己做。

【讨论】:

  • 很好,我很惊讶这样的指针在 Boost 中仍然不存在。如果它也可以拥有原始指针的所有权会更好,但无论如何我对此很满意。
  • @Anton3 这将只涉及添加一个驱逐舰调用者和一个void*any_block_base,将get_unsafe 移动到any_block_base,只有驱逐舰派生类型any_block_base。然后在实现案例中填充基础销毁器和 void ptr。开销稍大。
  • @Anton3 已发布增强和实时示例。
  • 甚至是删除器 :) 再次感谢!
  • any_block_dtor 中发现了一个错误:deleter 只被构造,但从未被破坏。应该存储普通的D dtor; 而不是dtor_data
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-27
  • 2013-06-12
  • 2013-04-16
  • 1970-01-01
  • 2012-09-01
  • 2011-09-13
相关资源
最近更新 更多