优势遗传 - 真的那么糟糕吗?

22

我是那种必须让代码在编译时不发生任何警告的人。通常情况下,我尊重编译器,如果它发出警告,我就会把它视为我需要稍微修改一下代码的信号。但是对于这个问题,我好像无法解决,而且据我所知,我没有做任何“坏事”。有人认为这是一种糟糕的设计吗?我看不出有什么特别糟糕的地方(除了“恶魔钻石”),但它是完全有效和有用的代码。但是它会在 MSVC 中生成二级警告!

class IFoo
{
public:
    virtual void foo() = 0;
};

class Bar : public virtual IFoo
{
public:
    virtual void foo() { std::cout << "Hello, world!"; }
};

class Baz : public virtual IFoo
{

};

class Quux : public Bar, public Baz
{

};

现在如果我创建一个Quux对象,它应该调用Bar::foo的实现。MSVC非常有帮助:它警告我不够模棱两可?

警告 C4250:“Quux”:通过支配继承了“Bar::Bar::foo”

现在我知道我可以使用#pragma关闭此警告,但这不是我在这里要问的问题。是否有理由我应该听从编译器的建议,或者这只是一个过度热心的警告?


1
老实说,在我看来,使用虚拟继承本身就是糟糕的设计。它过于复杂,而带来的好处却微乎其微。在使用它之前,我会认真考虑两次(甚至三四次)。 - Peter Alexander
1
代码可能不是很糟糕,但它很脆弱。依赖于模糊的消歧规则来选择要调用的函数并不会使代码易于阅读或维护。 - Bo Persson
4
“太复杂了”具体指什么方面的复杂? - curiousguy
3个回答

12

在执行虚拟继承时,不显式覆盖最终派生类中的每个成员是一个糟糕的想法。否则,当某人更改从虚拟基类继承的基类之一时,您就会导致代码崩溃。这样做并没有任何明显的问题,程序不会崩溃或出现其他问题,但它会影响可维护性。如果您想调用Bar::foo版本,则应该在Quux::foo中委托它。


我认为这是一个好点。对于我来说,把所有东西都移动到最派生的类中不太适合实际问题(当然,上面是一个简化的例子),因此我可能需要仔细检查我的整体设计。(我确实希望Bar类的用户)。 - Shirik
3
胡说八道。仅仅因为你不练习某件事并不意味着它是不可行的/糟糕的/等等。只需说:“我不习惯那样做。” - curiousguy
1
@curiousguy:我从没说过我不实践它,而且我也给出了一个非常充分的理由说明为什么这是不好的。如果你想表明我错了,那就给出一个客观的理由说明它为什么不是不好的。 - Puppy
3
"die a horrible death"的意思是每当你引入歧义时编译器会显示可怕的错误信息。非常可怕,哈哈。 - curiousguy
1
实际上,在这种情况下虚函数表会发生什么?foo() 方法会有一个入口还是两个入口?如果只有一个入口,并且 foo() 方法在 Bar 和 Baz 中都被重写,那么最终类的虚函数表会发生什么? - Serge
“不明确地覆盖最终派生类中的每个成员是一个糟糕的想法”,但在编写模板代码时,您并非总能这样做。支配规则存在是有原因的。“当有人更改您的基类之一时,您正在要求您的代码遭受可怕的死亡”,无论虚拟继承还是支配都在使用,这并不奇怪。 - n. m.

2

就您代码的可运行性而言,它只是提醒您 Bar 是 foo 的主要实现。它仅仅是为了通知您,并不是真正的警告,这样如果您在调试时认为是 Baz,也不会把自己的头发都拔光 :)


1
就我个人而言,我认为级别4的警告只是“提醒一下你”。而这个警告属于级别2,似乎更多地是在问“你在这里干什么……”。(作为比较,C4390是级别3,通常情况下都很糟糕。) - Shirik
3
我希望有一种编译器能够告知我编译程序时使用的每个不明显的C++规则。对于每个重载函数调用,会有大约5条消息;对于每个由重载决议调用的内置运算符,会有15条消息;对于每个模板重载函数调用,会有30条消息。编译日志会比程序源代码要大很多。 - curiousguy

1

你不写的原因是什么?

class Quux : public Bar, public Baz
{
    using Bar::foo;
};

?

这样可以让您获得相同的重用水平,而不会出现脆弱性。


6
不起作用,它仍然会抛出警告。VC++ 2013 - Maks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接