【问题标题】:C++ Cast to "uncle class"C++ 转换为“叔叔班”
【发布时间】:2019-03-31 05:55:26
【问题描述】:

我有一个Wrapper<T> 模板类。

我有一个基类 A。

我有一个派生类 B:public A。

我想要的是将Wrapper<B> 存储在vector<Wrapper<A>> 中。

我知道这在技术上是不对的,因为 Wrapper<B> 不是 Wrapper<A> 的直接子类

See inheritance diagram

但不知何故,STL 允许 shared_ptr 这样做:

#include <memory>
#include <vector>
#include <iostream>

struct A
{
  A() {}
  virtual ~A() {}
  virtual void print() { std::cout << "A" << std::endl; }
};

struct B : public A
{
  B() {}
  virtual ~B() {}
  virtual void print() { std::cout << "B" << std::endl; }
};

int main()
{
  std::vector<std::shared_ptr<A>> v;

  v.push_back(std::make_shared<A>());
  v.push_back(std::make_shared<B>());

  for (auto &e : v)
    e->print();

  return 0;
}

我怎样才能为我的Wrapper&lt;T&gt; 类实现相同的结果?

这是非编译代码:

#include <vector>
#include <iostream>

template <class T>
struct W
{
};

struct A
{
  A() {}
  virtual void print() { std::cout << "A" << std::endl; }
};

struct B : public A
{
  B() {}
  virtual void print() { std::cout << "B" << std::endl; }
};

int main()
{
  std::vector<W<A>> v;

  v.push_back(W<A>());
  v.push_back(W<B>());

  for (auto &e : v)
    e->print();

  return 0;
}

最好, 皮埃尔

【问题讨论】:

  • "我有一个 Wrapper 模板类。" 它是如何定义的?你想要什么功能?
  • 一个B is-a A,你不需要shared_ptr&lt;B&gt;,一个shared_ptr&lt;A&gt;可以容纳一个B实例。如果您需要这种区别,我会闻到糟糕的设计。
  • 显示问题的邮政编码。
  • @AlgirdasPreidžius Wrapper 是为了让问题保持简单而进行的简化。在我的真实世界用例中,我有一个 vector 包含 Product 和 ProductDerivative 对象(产品导数是 Product 的子类)。
  • 代码in your question应该显示问题。将该代码放入问题中,而不是有效的代码。

标签: c++ c++11 templates casting shared-ptr


【解决方案1】:

与 A 和 B 共享的 ptr 是不相关的类型。

shared ptr 所做的是提供一个转换构造函数。只要指针类型可以转换,它就接受 raw-pointer-to 和 shared-ptr-to。

shared ptr 类型会擦除如何进行最终销毁,并存储指针,所以这真的不难。

具体的工作方式取决于数据的存储方式和 Wrapper 的作用。

但是:

template<class T>
struct Wrapper {
  template<class U>
  Wrapper(Wrapper<U>);
};

基本上就是这样。也许一些 SFINAE 会得到更早的错误。

...

由于您实际上在 cmets 中添加了详细信息,因此您有:

template<class T>
struct RollbackElement : public T

作为Wrapper。答案是,不,你不能那样做。

你可以这样做:

template<class T>
struct RollbackElement : public std::any {
  operator T&() {
    return std::any_cast<T&>(*this);
  }
};

您可以在T 周围存储一个类型擦除包装器,而不是存储一个实际 T

template<class T>
struct RollbackElement : private std::any {
  operator T&() {
    return *get();
  }
  operator T const&() const {
    return *get();
  }
  RollbackElement( T in ):
    std::any(std::move(in)),
    get_impl([](std::any& x)->T*{return std::any_cast<T*>(&x);})
  {}
  RollbackElement():RollbackElement({}) {}
  template<class U>
  RollbackElement( RollbackElement<U> const& o ):
    std::any( static_cast<std::any const&>(o) ),
    get_impl(o.get_impl)
  {}
  // note: due to quirks in `std::any`, a move ctor isn't safe
  T* operator->() { return get(); }
  T const* operator->() const { return get(); }
  T* get() { return get_impl(*this); }
  T const* get() const { return get_impl(const_cast<std::any&>(*this)); }
private:
  std::function<T*(std::any&)> get_impl = {};
};

加上你想要的任何增强。

这里的诀窍是你的存储空间,std::any,可以存储任何东西。

我们将如何从std::any 获取到T* 存储在get_impl 中。

当我们复制 RollbackElement 时,我们同时复制 any(不强制转换为具体类型) get_impl。如果另一个 Rollback 是 不同的 类型,我们依靠 std::function 的转换构造函数来为我们调整返回的指针。

【讨论】:

  • 谢谢,由于公司政策,我的代码库被困在 C++11,所以 std::any 对我来说不是一个选项,但我可能可以使用转换构造函数来解决问题;)
  • @PierreWilmot boost::any 或自己编写(哎哟)。如果您想实际上存储派生而不是基,则需要std::any 的功能;基向量不同于伪装成基向量的派生向量。你可以使用std::shared_ptr&lt;T&gt;,但这会极大地改变语义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-05
  • 1970-01-01
  • 1970-01-01
  • 2023-02-23
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
相关资源
最近更新 更多