有关详细信息,请参见下面的代码,但是基本方案如下。我有一个容器(一个会话),可以在其中放入和取出对象。
类似于:
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);
}
请注意,会话可以存储异构类型,但是在store
和load
时,我静态地知道了变量的类型。
我的问题是,我遇到了这样一种情况,用户希望将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 ++中是否有很好的方法来处理这种情况。我环顾四周,没有发现任何符合我需要的东西。
我发现的关于异类容器的所有内容都始终假定为共享的基类,而我却不想要。这可能吗?
答案 0 :(得分:5)
您可以使用户向您提供正确的铸造路线,以供遵循:
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");
答案 1 :(得分:1)
解决方案由我的兄弟提供,他恰好是C ++专家,没有stackoverflow:)
这里是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());
}
答案 2 :(得分:0)
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;
}
其他解决方案需要使用std::any
,但这将不那么方便。
您的方法load
是模板有点令人不安。