【问题标题】:Best practice for casting an inherited object to the correct child class将继承对象强制转换为正确子类的最佳实践
【发布时间】:2018-11-19 10:03:28
【问题描述】:

我有两个班,每个班都有几个孩子:

class ContainerGeneral {...};
class ContainerTypeA : ContainerGeneral {
public:
    void doSomethingA();
};
class ContainerTypeB : ContainerGeneral {
    void doSomethingB();
};

class InterpreterGeneral {
protected:
    ContainerGeneral* container;
};

class InterpreterTypeA : InterpreterGeneral {
public:
    void saveContainer(ContainerTypeA* cont) {
        container = cont;
    }
};

class InterpreterTypeB : InterpreterGeneral {
public:
    void saveContainer(ContainerTypeB* cont) {
        container = cont;
    }
};

Interpreter 类用于对应类型的容器(AABBGeneralGeneral)。为此,我向InterpreterGeneral 添加了一个指向ContainerGeneral 对象的成员指针。我希望InterpreterGeneral 将此对象寻址为ContainerGeneral,但我希望继承的类能够将相同的容器寻址为适当类型的容器。我可以通过在寻址时将指针转换为继承的类来做到这一点(仅用于A 以节省空间的示例):

(ContainerTypeA*)container->doSomethingA();

或者通过添加一个继承类型的新成员指针,该指针将指向与容器相同的位置:

class InterpreterTypeA : InterpreterGeneral {
public:
    void saveContainer(ContainerTypeA* cont) {
        containerA = cont;
        container = cont;
    }
    void doSomething() {
        containerA->doSomethingA();
    }
private:
    ContainerTypeA* containerA;
};

在这种情况下,最佳做法是什么?有没有办法尽可能干净地做到这一点,无需每次都进行强制转换,也无需添加不包含任何“新”信息的新成员?

【问题讨论】:

  • 是的,在ContainerGeneral 中创建一个虚拟的doSomething,不要费心从InterpreterGeneral 继承。

标签: c++ inheritance


【解决方案1】:

如果您需要有关InterpreterTypeAInterpreterTypeB 中容器实例的具体类型信息,请在您的代码中通过不在GeneralInterpreter 中存储GeneralContainer* 数据成员来表达这一点(protected 数据成员在任何情况下),而是具体的ContainerTypeA*ContainerTypeB* 子类InterpreterTypeAInterpreterTypeB 中的数据成员。存储一个基类指针然后通过强制转换来规避它的限制隐藏了需要具体类型信息的事实。

此外,在容器基类中提供doSomethingA()doSomethingB()的空默认实现,或者将它们转换为virtual纯成员函数并将空实现转换为ContainerTypeAContainerTypeB。然后,调用它们是安全的——当它是不受欢迎的具体类型时,它们不会做任何事情。

最后一个迂腐的旁注:我看不出有任何理由在层次结构“子”类中调用派生类。常用术语是“子”类。

【讨论】:

  • 我接受了这个答案,因为第二部分,为 B 创建了 doSomethingA 的空实现,反之亦然。我需要一个指向父类中container 的指针,因为它使用了该类的通用函数,所以第一部分在这里不适用。
【解决方案2】:

您正在处理的问题有virtual functions 形式的解决方案。试试这个:

class ContainerGeneral {
public:
    virtual void doSomething() = 0;
};

class ContainerTypeA : public ContainerGeneral {
public:
    void doSomething() {
        std::cout << "Hello A!" << std::endl;
    };
};

class ContainerTypeB : public ContainerGeneral {
public:
    void doSomething() {
        std::cout << "Hello B!" << std::endl;
    };
};

这样你就完全不用费心从InterpreterGeneral继承了:

class InterpreterGeneral {
public:
    void doSomething()
    {
        container->doSomething();
    }
private:
    ContainerGeneral* container;
};

旁注:这当然会产生一些运行时开销。如果您在运行时不需要多态性,则可以避免它。看看the static polymorphism。仅当您是忍者时。

【讨论】:

  • 这个答案的问题在于它假设doSomethingAdoSomethingB 有一些共同点,而事实上,它们根本没有任何共同点。如果我能够拥有一个名称对所有孩子都有意义的函数,我会使用它,但这里没有。您可以假设容器是动物,其中一个是鸟,另一个是狗,doSomethingAlayAnEggdoSomethingBgoForAWalk。创建一个通用的函数名意味着该名称是没有意义的,并且会创建一个不可读的代码。
  • @SIMEL 你必须重新设计你的设计。你说它们是不相关的,但似乎你想在InterpreterGeneral 类中同样对待它们。这不加起来。
  • 它们是相关的,它们都是相同的类型并且有很多共享的功能。只是不同的部分有很大的不同,那些是二进制文件,以不同的格式表示相同的初始数据。所以“最终用户”具有相同的接口,并且一些二进制数据是相同的。但有些数据是完全不同的,不仅是相同的数据以不同的方式写入,而且每种格式都有另一种格式没有的数据。
  • @SIMEL 所以基本上每个类都是一个解析器,是吗?对于接受流并返回公共结构对象的虚函数来说,这听起来像是一个完美的例子。
  • 也看看the single responsibility principle。我鼓励使用它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-27
  • 2021-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-07
相关资源
最近更新 更多