我正在尝试为我正在开发的个人游戏引擎设计一个文件系统,但我很难在避免动态转换的同时使用多态性。我只是想分享我的一个想法,让大家对此提供反馈,也许也分享一些你们的想法。
我的游戏引擎中有不同类型的对象(例如,
Texture
、Mesh
等),它们都继承自类Object
。
class Object
{
};
class Texture : public Object
{
};
我有保存对象的文件的概念。
class File
{
public:
Object* GetObject() const { return m_Object; }
private:
Object* m_Object;
};
我希望能够为包含特定类型对象的文件定义命令,因此类
FileCommand
。
class FileCommand
{
public:
virtual void Execute(File* file) = 0;
};
现在,假设我们有一个名为
OpenFileInTextureEditorCommand
的类,它继承自 FileCommand
。显然,此类应该接收包含纹理的文件。但是,为了确保这一点,我们必须将类型为 file.GetObject()
的 Object*
的返回值转换为 Texture*
。
class OpenFileInTextureEditorCommand : public FileCommand
{
public:
void Execute(File* file) override
{
Texture* texture = std::dynamic_cast<Texture*>(file->GetObject());
if (texture)
{
// Open texture in texture editor...
}
}
};
我的设计所能实现的最好成绩就是伪装动态铸件。
所以,让我们重新使用类
Object
和类 Texture
现在,我已将类
File
转换为模板类。由于文件需要存储在std::vector
中,我被迫创建一个非模板化基类FileBase
。 File
的目的主要是处理动态铸造。
class FileBase
{
public:
Object* GetObject() const { return m_Object; }
protected:
Object* m_Object = nullptr;
};
template<class T>
class File : protected FileBase
{
public:
// Turns the class into an abstract class -- cannot be instanciated. Its only job to handle dynamic casting.
virtual ~File<T>() = default;
T* GetObject() const
{
// Camouflaged dynamic casting
return dynamic_cast<T*>(m_Object);
}
};
由于我的目标是避免在
FileCommand
实现中进行动态转换,因此我还将其转换为模板类。由于 FileCommand
也需要存储在 std::vector
中,因此我被迫创建一个非模板化基类 FileCommandBase
。与 File
一样,FileCommand
的目的主要是处理动态铸造。
class FileCommandBase
{
public:
virtual void Execute(FileBase* file) = 0;
};
template<class T>
class FileCommand : protected FileCommandBase
{
public:
// Turns the class into an abstract class -- cannot be instanciated. Tts only job is to handle dynamic casting.
virtual void Execute(File<T>* file) = 0;
protected:
void Execute(FileBase* file) override
{
// Camouflaged dynamic casting
Execute(dynamic_cast<File<T>*>(file));
}
};
最后,我们可以实现
OpenFileInTextureEditorCommand
,不再需要动态转换了。
class OpenFileInTextureEditorCommand : public FileCommand<Texture>
{
public:
void Execute(File<Texture>* file) override
{
Texture* texture = file.GetObject();
if (texture)
{
// Open texture in texture editor...
}
}
};
OpenFileInTextureEditorCommand
的代码本身并不是很短,但我认为它更干净。
尽管在写这篇文章时,我得出了一些结论:
现在,我可能过于执着地试图避免进行动态转换,但我看到人们在谈论动态转换的存在如何表明设计不好。我想听听您对动态铸造(或一般类型检查)的意见和经验。
谢谢!
我认为:
首先,
dynamic_cast
是关键字而不是方法,所以不需要std
。
其次,如果
Object
本身不是虚拟的,static_cast
就足够了。
第三,我们说“动态转换是糟糕设计的一个指标”,因为您可以将它们抽象为一个公共类,并使用所有必要的方法来显示它们的属性,以便多态性能够处理它们。然而,
Object
太高级、太抽象,它显然会隐藏对象的许多独特特征,并且很难适当地执行多态性。因此,在这种情况下,即您有一些非常通用的类,并且您严重依赖它们,动态转换是完全可以接受的。这是更多 OOP 语言中的常见情况,例如在 C# 中,称为 boxing 和 unboxing 表示强制转换为 object
,反之亦然。
最后,如果没有多重继承,
dynamic_cast
可能会有不错的性能;在最好的情况下,它可能只比static_cast
慢一点,所以如果你真的需要它,这可能不是一个大问题(除非你的分析器告诉你不需要......)。你可以看这个blog的分析。