C++ - 多态性和动态转换的困难时期

问题描述 投票:0回答:1

我正在尝试为我正在开发的个人游戏引擎设计一个文件系统,但我很难在避免动态转换的同时使用多态性。我只是想分享我的一个想法,让大家对此提供反馈,也许也分享一些你们的想法。

问题

我的游戏引擎中有不同类型的对象(例如,

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
的代码本身并不是很短,但我认为它更干净。

尽管在写这篇文章时,我得出了一些结论:

  • 比以前更多的动态铸造,除了它以某种方式隐式
  • 比以前更多的类,可能更难维护

现在,我可能过于执着地试图避免进行动态转换,但我看到人们在谈论动态转换的存在如何表明设计不好。我想听听您对动态铸造(或一般类型检查)的意见和经验。

谢谢!

c++ casting architecture polymorphism game-engine
1个回答
0
投票

我认为:

  • 首先,

    dynamic_cast
    是关键字而不是方法,所以不需要
    std

  • 其次,如果

    Object
    本身不是虚拟的,
    static_cast
    就足够了。

  • 第三,我们说“动态转换是糟糕设计的一个指标”,因为您可以将它们抽象为一个公共类,并使用所有必要的方法来显示它们的属性,以便多态性能够处理它们。然而,

    Object
    太高级、太抽象,它显然会隐藏对象的许多独特特征,并且很难适当地执行多态性。因此,在这种情况下,即您有一些非常通用的类,并且您严重依赖它们,动态转换是完全可以接受的。这是更多 OOP 语言中的常见情况,例如在 C# 中,称为 boxingunboxing 表示强制转换为
    object
    ,反之亦然。

  • 最后,如果没有多重继承,

    dynamic_cast
    可能会有不错的性能;在最好的情况下,它可能只比
    static_cast
    慢一点,所以如果你真的需要它,这可能不是一个大问题(除非你的分析器告诉你不需要......)。你可以看这个blog的分析。

© www.soinside.com 2019 - 2024. All rights reserved.