【问题标题】:Override contra-variance workaround needed需要覆盖逆变换解决方法
【发布时间】:2011-08-22 08:38:05
【问题描述】:

我很难找到(我确信这是一种非常常见的)设计模式来解决以下问题。考虑这段代码:

class AA {};
class BB : public AA {};

class A
{
public:
    virtual void foo(AA& aa) = 0;
};

class B : A
{
public:
    void foo(BB& bb){cout<<"B::foo"<<endl;}
};

int main()
{
    B b;
    BB bb;
    b.foo(bb);
}

此代码不会编译,因为 B 类没有覆盖纯虚函数 'foo'。编译器将 B 声明的 foo 仅视为对 foo 的重载,因为在覆盖函数的输入参数中不允许协变。

现在,我明白了其中的原因。 B 继承自 A 的事实意味着它应该能够处理任何带有 AA 类型参数的 foo 调用,而前面的代码没有给出处理除 BB 之外的任何参数类型的实现。

当然,我可以在 B 的 foo 实现中将 aa 强制转换为 BB,但我正在寻找一种保留类型安全性并实际上强制 B 类的实现者也按顺序实现从 AA 继承的类的解决方案用于编译代码。在一个理想的世界里,我可以写出类似这样的伪代码:

class A
{
public:
    abstract class AA{}; //indicates that any child of A must implement also child of AA
    abstract void foo(AA& aa);
};

class B : public A
{
public:
    class BB : AA{}; //will not compile without this
    void foo(BB& bb){cout<<"B::foo"<<endl;}
};

有没有办法在 C++ 中实现类似的功能? (可能通过某种映射对象而不需要继承来提升)

请注意,实际上(与示例不同),BB 和 AA 之间的继承至关重要,因为 AA 有许多共享许多品质的孩子,最后我想要完成的是迭代 A 类的向量并仅使用适当的参数(AA 向量)运行“foo”

【问题讨论】:

  • 您打算按值传递多态对象吗?你会遇到切片问题......你应该通过AA&amp; aaBB&amp; bb
  • 为什么foo 在这种情况下必须是虚拟的?
  • @Andreas:我希望所有从 A 继承的类都必须实现自己的 foo 版本,因此它是纯虚拟的
  • 你可能希望你的所有继承都是public,而不是(隐式)private。否则你的问题将没有意义,例如BB&amp; 不会隐式转换为 AA&amp;,如果没有特殊访问权限,您不能将 AA&amp; 向下转换为 BB&amp;

标签: c++ overriding virtual covariance contravariance


【解决方案1】:

为了提供类型安全,您应该使用模板而不是继承。

class AA {};
class BB : AA {};

template <typename Managed> class FooManager {
    virtual void foo(Managed& m) { /* default implementation */ }
};

class B : public FooManager<BB> {
    void foo(BB bb) { cout << "B:foo()" << endl; }
};

后面的代码,比如要遍历一个数组,

template<typename Managed> void processAll(vector<Managed> v, FooManager<Managed> mgr) {
    for(Managed& m : v) mgr.foo(m);
}

B b;
vector<BB> bbs;
processAll(bbs, b);

编辑:错字修复

【讨论】:

  • 这其实是我之前的设计。这里的主要缺陷是你失去了 foo 的变质。使用这种设计,您不能遍历 FooManager 和 AA 的向量,并且只有在存在一致性时才安全地调用 foo,因为 foo 可以具有任何类型的参数,而不仅仅是从 AA 派生的类型。我发现这个解决方案对于我的团队的需求来说太弱了
  • @eladidan 我认为有解决这个“缺陷”的方法。您可以确保实际的模板参数符合您的要求。
  • 尴尬。在我之前的评论中,我的意思是多态而不是变质。学过的知识。当您饿到要吃键盘时,切勿发表评论...
  • @jv42:你能扩展一下吗?我该怎么做呢? (也许写一个答案......)。谢谢
  • @eladidan 由于我目前更喜欢 C#,我宁愿让 C++ 大师解释“模板参数约束”的 C++ 实现。搜索发现:stackoverflow.com/questions/122316/template-constraints-c
【解决方案2】:

您可以查看访问者模式。这是“双重调度”类型问题的一般解决方案(基于对象消息调度到虚函数)。

即把foo()放入一个访问者,将A::foo()重命名为A::Visit(FooVisitor&amp; )

编辑:澄清一下,这可能有助于解开层次结构的目的。如果您考虑一下,您正在尝试根据另一个(AB)来建模一个层次结构(AABB)的关系。这对于建模,甚至从概念上考虑都非常尴尬。

要将其重新建模为访问者,您通常会将其中一个层次结构转换为单个类,而是对您可以通过访问者在算法层次结构中对该类执行的操作进行建模.这更加健壮,因为它强制您显式地实现层次关系的每个组合,并且如果您稍后修改层次结构,它将在编译时中断(很好)。

class A; class B;
struct AVisitor 
{ 
    virtual ~AVisitor() { } 

    virtual void Visit(A& ) = 0;
    virtual void Visit(B& ) = 0;
};

class A
{
public:
    virtual ~A() { }

    virtual void Visit(AVisitor & visitor) { visitor.Visit(*this); }
};

class B : public A
{
public:
    virtual void Visit(AVisitor & visitor) { visitor.Visit(*this); }
};

struct PrintingVisitor : public AVisitor
{
    void Visit(A& a){cout<<"A::foo"<<endl;}
    void Visit(B& b){cout<<"B::foo"<<endl;}
};

int main()
{
    B b;
    PrintingVisitor printer;
    b.Visit(printer);
}

【讨论】:

  • 这个解决方案不能很好地转化为我的问题的领域,因为在我的情况下,从 A 派生的类本身就是算法对象,而 foo 是它们的主要函数,它在派生类型的输入上运行算法来自 AA。从它们中删除主要算法会完全取消对该类层次结构的使用。此外,对于 A 的每个孩子,不会有超过一个 foo 的实现,所以我永远不会创建多个 AVisitor 的孩子
  • 很公平——如果你不想抽象掉 foo 操作,访问者就会失去它的大部分用处。但是,如果您不想多态地处理算法,那么继承可能不是正确的方法……也许上面 grep 的答案中的模板-y 更适合
  • @eladian,您是否考虑过将A 层次结构Strategies?即FooStrategyAFooStrategyB,它们被传递给BB ctor 并由BB 直接使用?
  • Strategies DP 对于我们的设计来说是多余的,因为对于从 A 派生的类来说,foo 实际上是唯一重要的操作。如果我想要更好的答案,也许我应该稍微解释一下我的问题空间。我正在设计一个过滤器管道,与 DirectShow 过滤器非常相似,只有管道在运行时是静态的。 A 类是 BaseFilter 类(纯虚拟),从它派生的类是过滤器实现。 AA 类是基础 I/O 类,每个过滤器都有不同的输入/输出。我目前正在研究 DirectShow 设计以供参考
猜你喜欢
  • 2015-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-04
  • 2014-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多