有没有一种方法可以前向声明协变性?

19

假设我有这些抽象类 FooBar

class Foo;
class Bar;

class Foo
{
public:
  virtual Bar* bar() = 0;
};

class Bar
{
public:
  virtual Foo* foo() = 0;
};

假设我有派生类ConcreteFooConcreteBar。 我想像这样协变地细化foo()bar()方法的返回类型:

class ConcreteFoo : public Foo
{
public:
  ConcreteBar* bar();
};

class ConcreteBar : public Bar
{
public:
  ConcreteFoo* foo();
};

我们心爱的单遍编译器并不知道 ConcreteBar 将继承自 Bar,因此这段代码无法编译通过,而 ConcreteBar 则是一种合法的协变返回类型。对 ConcreteBar 进行简单的前向声明也不起作用,因为它并没有告诉编译器任何与继承相关的信息。

这是 C++ 的一个缺陷,还是有办法绕过这个难题呢?


很多人认为协变是不必要的 - 可以参考这个问题https://dev59.com/PnM_5IYBdhLWcg3wt1k0,就我而言,它并没有引发令人信服的答案。 - anon
2
我正在处理一个有大量现有代码的项目。仅通过协变地更改一些方法的返回类型,我就能够摆脱许多静态转换。如果我有一个令人信服的解决方案来解决上述问题,我甚至可以摆脱更多的问题。 - Tobias
4个回答

5
你可以很容易地伪造它,但你会失去静态类型检查。如果你用static_casts替换dynamic_casts,那么你就拥有了编译器内部使用的内容,但你将没有动态或静态类型检查:
class Foo;
class Bar;

class Foo
{
public:
  Bar* bar();
protected:
  virtual Bar* doBar();
};

class Bar;
{
public:
  Foo* foo();
public:
  virtual Foo* doFoo();
};

inline Bar* Foo::bar() { return doBar(); }
inline Foo* Bar::foo() { return doFoo(); }

class ConcreteFoo;
class ConcreteBar;
class ConcreteFoo : public Foo
{
public:
  ConcreteBar* bar();
protected:
  Bar* doBar();
};

class ConcreteBar : public Bar
{
public:
   ConcreteFoo* foo();
public:
   Foo* doFoo();
};

inline ConcreteBar* ConcreteFoo::bar() { return &dynamic_cast<ConcreteBar&>(*doBar()); }
inline ConcreteFoo* ConcreteBar::foo() { return &dynamic_cast<ConcreteFoo&>(*doFoo()); }

2
这个方案可行,但肯定不会赢得美丽大赛 :) - Tobias
如果参与者被限制为解决所述问题的人,我对结果并不确定 :-) 显然,您可以使用提供的协方差语言之一来表示其中一个类,但我更喜欢保持对称性。 - AProgrammer
2
@Tobias:我认为原始设计中的复杂依赖关系也不会赢得美观大赛。 :P - David Rodríguez - dribeas

4
静态多态性难道不能解决你的问题吗?通过模板参数将派生类传递给基类,这样基类就会知道派生类型并声明适当的虚函数了吧?

3
这个怎么样?
template <class BarType>
class Foo
{
public:
    virtual BarType* bar() = 0;
};

template <class FooType>
class Bar
{
public:
    virtual FooType* foo() = 0;
};

class ConcreteBar;
class ConcreteFoo : public Foo<ConcreteBar>
{
public:
    ConcreteBar* bar();
};

class ConcreteBar : public Bar<ConcreteFoo>
{
public:
    ConcreteFoo* foo();
};

2

协变性基于继承图,因此由于您无法声明父类是协变的,所以子类也不能。

class ConcreteBar : public Bar;

因此无法告诉编译器协变性。但是你可以借助模板来实现,将ConcretFoo::bar声明为模板,后绑定允许您解决此问题。

我知道你不能前向声明继承。而且模板也无济于事,因为模板成员函数不能是虚拟的。 - Tobias
不完全正确:这是我的样例,它可以工作!<pre><code> class ConcreteFoo : public Foo { public: template <class T> T* bar(); }; template <> ConcreteBar* ConcreteFoo::bar<ConcreteBar>(){} </code></pre> - Dewfy
Dewfy:请在您的回答中包含一个可工作的示例,我无法从您提供的代码构建一个。http://codepad.org/xl4NTdQt - Roger Pate
@Roger Pate完成了,请查看http://codepad.org/xl4NTdQt#comment-ayWjjk4p,示例在msvc、g++上工作。 - Dewfy
啊,谢谢,这使ConcreteFoo成为一个模板,而不是你最初说的ConcreteFoo :: bar。 - Roger Pate

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