【问题标题】:Polymorphic casting from void*来自 void 的多态铸造*
【发布时间】:2019-01-03 14:13:32
【问题描述】:

详情见下方代码,但底层场景如下。我有一个容器(一个会话),我可以将对象放入其中并从中取出。

类似于:

 std::shared_ptr<Tiger> t = ...;
 session.store("tigers/1", t);

 std::shared_ptr<Tiger> t2 = session.load<Tiger>("tigers/1");

两个函数都定义为:

 class Session { 
      template<class T> 
      void store(std::string id, std::shared_ptr<T> instance);

      template<class T> 
      std::shared_ptr<T> load(std::string id);
 }

请注意,会话可以存储异构类型,但在 storeload 时间我静态知道变量的类型是什么。

我的问题是我遇到了这样一种情况,即用户想要将Tiger 放入会话中,但改为检查基本类型。例如:

  session.load<Animal>("tigers/1");

现在,我在会话中有效地将数据存储为void*,并使用reinterpret_cast 将它们恢复为用户提供的类型。这... 工作,只要一切都是微不足道的,但是当我们遇到稍微复杂的情况时,我们就会遇到问题。

这是演示我的问题的完整代码:

struct Animal
{
    virtual void Pet() const = 0;
};

struct IJumpable
{
    virtual void Jump() const = 0;
};

struct Tiger : Animal, IJumpable
{
    void Pet() const override
    {
        std::cout << "Pet\n";
    }

    void Jump() const override
    {
        std::cout << "Jump\n";
    }
};

int main()
{
    auto cat = std::make_shared<Tiger>();

    // how the data is stored inside the session
    auto any_ptr = std::static_pointer_cast<void>(cat);
    // how we get the data out of the session
    auto namable = std::static_pointer_cast<IJumpable>(any_ptr);

    namable->Jump();
    std::cout << std::endl;
}

如果您运行此代码,您会看到它运行,但它不是调用Jump,而是调用Pet。我知道这是因为使用了错误的虚拟方法表,因为我在 `void* 上有效地调用了reinterpret_cast

我的问题是在 C++ 中是否有处理这种情况的好方法。我环顾四周,没有看到任何符合我需要的东西。

我发现的有关异构容器的所有内容都假定为共享基类,而我没有也不想这样做。这可能吗?

【问题讨论】:

  • 确定在Session 类中,您将只存储Animal 的基本类型吗?
  • 如果你事先知道你存储的类型,也许你想看看 std::variant。
  • 存储类型是静态已知的吗?如果是这样,您可以使用std::variant吗?如果没有,可以使用std::any吗?
  • @KrzysiekKarbowiak 我不能使用std::any,因为类型不匹配。 auto cat = new Tiger(); auto any_ptr = std::any(cat); auto namable = std::any_cast&lt;IJumpable*&gt;(any_ptr); 这将导致错误的演员表
  • 我对存储类型很好,但我不知道如何 这样做或如何正确取出它。我想过使用 lambda 来捕获类型,但我不知道如何正确获取目标类型

标签: c++


【解决方案1】:

您可以让用户为您提供正确的铸造跋涉:

class Session { 
      template<class T> 
      void store(std::string id, std::shared_ptr<T> instance);

      template<class T> 
      std::shared_ptr<T> load(std::string id);

      template<class Stored, class Retrieved>
      std::shared_ptr<Retrieved> load_as(std::string id) {
          auto stored = load<Stored>(id);
          return std::static_pointer_cast<Retrieved>(stored);
      }
 }

这在调用者站点造成了混乱的使用,但信息必须来自某个地方:

auto shere_khan = make_shared<Tiger>();
session.store("tigers/1", shere_khan);
auto bagheera = session.load_as<Tiger, IJumpable>("tigers/1");

【讨论】:

  • 可以用这种方式存储图元吗?
  • 好的。知道了。谢谢。
【解决方案2】:

由我的兄弟提供的解决方案,他恰好是没有 stackoverflow 的 C++ 专家 :)

这是一个void_ptr 实现,它使用异常处理来启用多态转换来发现类型。性能应该接近 dynamic_cast。您应该能够使用std::type_index 优化上述内容并缓存偏移量。

#include <stdio.h>

class void_ptr {
  void* obj;
  void (*discover_type)(void*);

  template<typename T>
  static void throw_typed_object(void* obj)
  {
    T* t = static_cast<T*>(obj);
    throw t;
  }
public:

  void_ptr() : obj(0) {}

  template<typename T>
  void_ptr(T* t) : obj(t), discover_type(throw_typed_object<T>)
  {
  }

  template<typename T>
  T* cast() const
  {
    try {
      discover_type(obj);
    } catch(T* t) {
      return t;
    } catch(...) {
    }
    return 0;
  }
};

struct Animal {
  virtual ~Animal() {}
  virtual const char* name() { return "Animal"; }
};

struct Speaker {
  virtual ~Speaker() {}
  virtual const char* speak() { return "hello"; }
};

struct Lion : public Animal, public Speaker {
  virtual const char* name() { return "Lion"; }
  virtual const char* speak() { return "Roar"; }
};


int main()
{
  void_ptr ptr(new Lion());


  Animal* a = ptr.cast<Animal>();
  Speaker* s = ptr.cast<Speaker>();

  printf("%s\n", a->name());
  printf("%s\n", s->speak());
}

【讨论】:

  • 哇,我不确定我是敬畏还是害怕这段代码。这将需要一些沉思。很酷
  • 感谢@AyendeRahien - 更新代码以抛出和捕获指针而不是引用,因为调用了 ref 复制构造函数。
【解决方案3】:

IMO 最佳解决方案不是将指针转换为指向 void 的指针,而是转换为其他类型,可以将 dynamic sidecast 转换为所需类型。

#include <iostream>
#include <memory>

struct Animal
{
    virtual ~Animal() {}
    virtual void Pet() const = 0;
};

struct IJumpable
{
    virtual ~IJumpable() {}
    virtual void Jump() const = 0;
};

struct IStrorable
{
    virtual ~IStrorable() {}
};

struct Tiger : Animal, IJumpable, IStrorable
{
    void Pet() const override
    {
        std::cout << "Pet\n";
    }

    void Jump() const override
    {
        std::cout << "Jump\n";
    }
};

int main()
{
    auto cat = std::make_shared<Tiger>();

    auto any_ptr = std::static_pointer_cast<IStrorable>(cat);

    auto namable = std::dynamic_pointer_cast<IJumpable>(any_ptr);

    namable->Jump();
    std::cout << std::endl;
}

Live example

其他解决方案需要使用std::any,但这不太方便。

您的方法load 是一个模板,这有点令人不安。

【讨论】:

  • 我的问题是我不控制提供给我的类型(它们是由调用者完成的,我不想限制它们)
  • 您将如何为load 方法提供类型,因为它需要能够提供任何类型的返回?
猜你喜欢
  • 1970-01-01
  • 2014-05-29
  • 2013-05-27
  • 1970-01-01
  • 1970-01-01
  • 2011-04-20
  • 1970-01-01
  • 2018-01-06
  • 1970-01-01
相关资源
最近更新 更多