我希望标题不会太混乱。我所拥有的是一个类StorageManager
,其中包含从Storage
派生的类对象列表。这是一个例子。
struct Storage {}; // abstract
class StorageManager
{
private:
map<string, unique_ptr<Storage>> List; // store all types of storage
public:
template <typename T>
void Add(string Name) // add new storage with name
{
List.insert(make_pair(Name, unique_ptr<Storage>(new T())));
}
Storage* Get(string Name) // get storage by name
{
return List[Name].get();
}
};
说Position
是一种特殊的存储类型。
struct Position : public Storage
{
int X;
int Y;
};
感谢我last question上的优秀答案,Add
功能已经有效。我想改进的是Get
功能。它合理地返回一个指针Storage*
,我可以像下面这样使用它。
int main()
{
StorageManager Manager;
Manager.Add<Position>("pos"); // add a new storage of type position
auto Strge = Manager.Get("pos"); // get pointer to base class storage
auto Pstn = (Position*)Strge; // convert pointer to derived class position
Pstn->X = 5;
Pstn->Y = 42;
}
有没有办法通过自动返回指向派生类的指针来摆脱这个指针转换?也许使用模板?
答案 0 :(得分:2)
使用:
template< class T >
T* Get(std::string const& name)
{
auto i = List.find(name);
return i == List.end() ? nullptr : static_cast<T*>(i->second.get());
}
然后在你的代码中:
Position* p = Manager.Get<Position>("pos");
答案 1 :(得分:2)
除了@BigBoss已经指出的内容之外,我没有看到您可以为Get
成员函数做些什么,但是您可以改进Add
成员以返回已用存储空间。
template <typename T>
T* Add(string Name) // add new storage with name
{
T* t = new T();
List.insert(make_pair(Name, unique_ptr<Storage>(t)));
return t;
}
// create the pointer directly in a unique_ptr
template <typename T>
T* Add(string Name) // add new storage with name
{
std::unique_ptr<T> x{new T{}};
T* t = x.get();
List.insert(make_pair(Name, std::move(x)));
return t;
}
编辑临时阻止了我们dynamic_cast
。
EDIT2 实施MatthieuM的建议。
您还可以通过接受值来进一步改进功能 要插入的类型,使用默认参数,但可能会产生 另外的拷贝。
答案 2 :(得分:1)
除了这看起来像一个可怕的想法之外......让我们看看我们可以做些什么来改善这种情况。
=&GT;要求默认构造
是个坏主意template <typename T>
T& add(std::string const& name, std::unique_ptr<T> element) {
T& t = *element;
auto result = map.insert(std::make_pair(name, std::move(element)));
if (result.second == false) {
// FIXME: somehow add the name here, for easier diagnosis
throw std::runtime_error("Duplicate element");
}
return t;
}
=&GT;盲目低调是个坏主意
template <typename T>
T* get(std::string const& name) const {
auto it = map.find(name);
return it != map.end() ? dynamic_cast<T*>(it->second.get()) : nullptr;
}
但坦率地说,这个系统充满了漏洞。首先可能是不必要的。我鼓励您回顾一般问题,提出更好的设计。
答案 3 :(得分:1)
当你有一个指针或对某个类的对象的引用时,你所知道的是它引用的实际运行时对象是该类或某个派生类。 auto
在编译时无法知道对象的运行时类型,因为包含auto
变量的代码段可能位于运行两次的函数中 - 一次处理一个运行时类型的对象,另一个处理具有不同运行时类型的对象!类型系统无法告诉您具有多态性的语言中正在使用的确切类型 - 它只能提供一些约束。
如果您知道对象的运行时类型是某个特定的派生类(如您的示例所示),则可以(并且必须)使用强制转换。 (最好使用static_cast<Position*>
形式的强制转换,因为强制转换是危险的,这样可以更容易地在代码中搜索强制转换。)
但总的来说,这样做很多都是设计不佳的标志。声明基类并从中派生其他类类型的目的是使这些类型的 all 对象以相同的方式处理,而不会转换为特定类型。
Position
可能无法获得Storage
。StorageManager::Get()
的调用者需要对Position
执行的所有事情都可以通过调用未指定Position
的函数来完成 - 具体信息(例如坐标),您可以将这些功能转换为Storage
中的虚拟功能,并在Position
中实现Position
- 特定版本的功能。例如,您可以创建一个函数Storage::Dump()
,将其对象写入stdout
。 Position::Dump()
会输出X
和Y
,而其他可以设想的派生类的Dump()
实现会输出不同的信息。boost::variant<>
是一个很好的方法。该库提供了一种称为访问者模式的强大机制,允许您指定应为variant
对象可能采用的每种类型采取的操作。