【问题标题】:Multiple inheritance casting from base class to a different derived class从基类到不同派生类的多重继承转换
【发布时间】:2013-03-06 08:57:25
【问题描述】:

我有以下一组类:

还有下面这段代码:

A* a;
if(condition)
{
    a = new E();
}
else
{
    a = new D();
}

既然有F::foo()这样的函数,为了调用它,我必须将a转换为E*D*

if(condition)
{
    ((E*)a)->foo();
}
else
{
    ((D*)a)->foo();
}

据我所知,将a 转换为F* 以调用F::foo 是非法的,因为aA* 类型;对我来说,在调用foo 之前检查条件听起来像是一个设计问题。有人可以就如何改进这个类层次结构提供一些建议吗?

附言用this tool画图。

【问题讨论】:

  • 您需要通过虚函数实现动态多态性,然后实现将完成调用正确函数的所有艰苦工作。
  • 出于兴趣,您为什么首先将您的new E() 分配给A*?下面的答案中讨论了几种不同的方法。其中一些会更改您现有的类层次结构,而另一些则不会。可能是这个关于A* 的问题的答案导致了一个特定的解决方案。
  • @SteveJessop 我应该使用一个公共基类,它是AF 来实例化ED。建议F 的答案可能来自A,反之亦然不符合我的设计要求,因为FA 的功能无关紧要。 F 不是接口,ED 都需要从它继承。我想我也可以选择F 作为通用基类,但是我应该在需要时将a 转换为ACB。到目前为止,您的答案似乎是最好的解决方案。
  • 如果F 不是一个接口,那么为什么接收A* 的代码期望它有一个名为Foo 的函数呢?我提议的课程G 给了它一个期望的理由,或者另一条路线是放弃多态性,接受它想要的是“DE”,然后传递一个boost::variant<D*,E*> .这是最后的手段,但它可能是对您的设计的唯一正确描述。如果您的设计是必须将函数传递给DE,那么仅仅因为A 恰好是两者的共同基数就传递给它A* 并不能反映设计。
  • @SteveJessop 使用G 类也是一个好主意。谢谢。

标签: c++ casting multiple-inheritance


【解决方案1】:
#include <iostream>

struct A { virtual ~A() {} };

struct C : virtual A {};

struct B : virtual A {};

struct F {
    virtual void Foo() { std::cout << "ok\n"; }
};

struct E : C, virtual F {};

struct D : B, virtual F {};


int main() {
    A *a = new E();
    dynamic_cast<F*>(a)->Foo();
}
  • 如果你搞砸了,a 的引用不是F 的实例,那么 dynamic_cast 返回 null
  • 如果您不使用虚拟继承,那么您最终可能会得到模棱两可的基类。 dynamic_cast 到一个模棱两可的基础将失败(返回 null)。在此示例中,没有不明确的碱基,但您必须注意这一点。
  • 我在大多数类中都省略了虚拟析构函数,但这只是因为我很懒。

如果您反复发现自己处理的对象既是 AF 的实例,那么如果可能,这应该反映在类层次结构中。例如,您可以定义一个类型G,它实际上继承自AF。然后DE 可以从G 继承而不是F,并且您可以将G* 传递给这个期望A* 可以调用Foo() 的代码。

【讨论】:

  • 没错,我也刚刚尝试过这个 - 在 gcc 上,即使问题中的 C 风格转换也有效 - 那会是 UB 吗?
  • @Andreas:static_castE*D* 都可以(当然,您需要能够评估与创建对象时相同的条件,才能转换为正确的类型)。但是,您不能将 static_castA* 转换为 F*,因为它们是不相关的类。
  • @Andreas:刚刚意识到,我实际上并没有回答你的问题!转换为E*D* 的C 风格是static_cast,因此它具有定义的行为。由于static_cast 不存在,C 样式转换为F* 将是reinterpret_cast,并且当用于调用Foo() 时,that 会引发 UB。
  • 这种情况下不需要虚拟继承。
  • 非常感谢 - 我也只需要重新阅读我之前部分误解的问题 - 无论如何,+1 以获得好的答案和好的解释!
【解决方案2】:

如果不知道你的类的确切语义,很难给你设计建议(字母只是符号,所以必须假设这些继承关系是正常的,而它们可能不是)。

看看你的模型的正式组织,我会说你可以给A添加一个虚函数,DE都会覆盖它。然后,这些覆盖会将实现委托给F::foo()

class A { 
public:
    virtual void bar() { }; // Maybe make this pure if A is abstract
    // ...
};

// ...

class D : public C, public F { 
public:
    virtual void bar() { /* ... */ f::foo(); /* ... */ }
    // ...
};

class E : public B, public F { 
public:
    virtual void bar() { /* ... */ f::foo(); /* ... */ }
    // ...
};

【讨论】:

  • 这会污染接口AF 的功能。如果这是可以接受的,那么只需将F 的所有虚函数添加到A,而忘记F
【解决方案3】:

不知道不同类的角色,很难 说,但如果 AF 是接口(可能是这种情况),那么 给出一个A*,正确的方法是询问对象是否也 支持接口Fdynamic_cast&lt;F*&gt;。这给 您指向F 接口的指针(如果支持),以及 否则为空指针。

除此之外,您可能会反映F 接口是否扩展 A 接口,或者是否完全不相关。如果它 是一个扩展,那么F 应该可能派生自A;什么时候 创建一个已知实现扩展接口的对象, 您将其地址分配给F*,并避免所有未来的演员表。 (一般来说,在达到某个点之前不要分配给A* 其中一些指向的对象将实现F。) 所以你最终会得到类似的东西:

//  interfaces...
class A {};
class F : public virtual A {};

//  implementations of A...
class C : public virtual A {};
class B : public virtual A {};

//  implementations of F (and also A, of course)
class E : public C, public virtual F {};
class D : public B, public virtual F {};

注意,当从接口派生时,一般是 使推导虚拟化是个好主意。 (在这种情况下,它 A 的所有派生都需要。但由于同样 模式可以在另一个层次上重复,有一些新的类 扩展F的接口,一般来说比较简单 采用规则:从接口派生是虚拟的。)

如果F 真的与A 无关,那么您甚至可能会问什么 一类正在实现两者。或者,如果有意义的话 A 的一些(很多?)实现也实现了F,你 可能会考虑提供对F 的访问作为界面的一部分 A:说一个虚函数F* A::getF() { return NULL; }; 同样实现F 的类将覆盖此函数 类似于F* E::getF() { return this; }

【讨论】:

  • 我同意这一切。我们的两个答案从同一个地方开始,然后根据这些课程的真实细节提供从可能性领域略有不同的选项。 “从接口派生是虚拟的”这一规则很重要。
  • @SteveJessop 是的。有趣的是,您是唯一提到虚拟继承的其他回答者。难道我们是仅有的两个使用 C++ 作为 OO 语言(在其他范式中)的人。 (我们似乎也是唯一两个将AF 视为接口的人。)
  • 是的,界面很有趣。我不认为所有 C++ 程序员,即使是那些使用动态多态性的程序员,都真正区分接口和其他基础。对我来说,A* 被传递并且接收代码想要调用F 的函数这一事实意味着AF 接口(或者如果@987654352 @ 是一个混合然后Foo 需要在某个接口中,我们应该添加)。如果他们也有一些实现纠缠在一起,那么,从代码组织 POV 来看,这可能是不幸的,但这并不能阻止它们成为接口 :-)
  • @SteveJessop 如果您使用合约编程,一些代码通常在接口中。 (纯)虚函数应该是私有的,具有前置条件和后置条件的断言以及定义接口的非虚拟公共函数中的类不变量,并转发给私有虚函数。
  • 同意。我并没有声称对“实现”和“框架/样板”之间的区别有一个正式的定义,但是当我看到它时,我基本上就知道了。
【解决方案4】:

如果F 只是一个实现细节,那么你应该按照@AndyProwl 所说的去做。在基类A中创建一个虚函数。

如果F只是一个实现细节,另一种方法是将要处理的对象列表保留为Fs,并将要处理的对象保留为As。同样,正如 Andy 所说,这将取决于您的情况的语义。

vector<F*> effs;
vector<A*> ehs;

A* a;
F* f;
if(condition) {
    E* e = new E();
    a = e;
    f = e;
}
else {
    D* d = new D();
    a = d;
    f = d;
}

effs.push_back(f);
ehs.push_back(a);

for(A* a: ehs) {
    a->bar();
}
for(F* f: effs) {
    f->foo();
}

【讨论】:

  • 如果F 只是一个实现细节(例如F 是一个提供一些通用实现的具体类),这就是mixin 模式,他不需要(也不应该有) ) 指向F 的指针。
  • @JamesKanze 是的,这就是我的意思。如果F 不是实现细节,另一种选择是......我会更新。
猜你喜欢
  • 2012-12-10
  • 2014-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-23
  • 1970-01-01
相关资源
最近更新 更多