【问题标题】:Returning abstract type in base class在基类中返回抽象类型
【发布时间】:2011-12-16 23:07:39
【问题描述】:

在类层次结构的设计中,我使用了一个抽象基类,它声明了派生类将实现的各种方法。从某种意义上说,基类与 C++ 中的接口一样接近。但是,有一个具体问题。考虑下面声明我们的接口类的代码:

class Interface {
public:
    virtual Interface method() = 0;
};

class Implementation : public Interface {
public:
    virtual Implementation method() { /* ... */ }
};

当然,这不会编译,因为您不能在 C++ 中返回抽象类。为了解决这个问题,我使用了以下解决方案:

template <class T>
class Interface {
public:
    virtual T method() = 0;
};

class Implementation : public Interface<Implementation> {
public:
    virtual Implementation method() { /* ... */ }
};

这个解决方案很有效,而且一切都很好,但对我来说,它看起来不是很优雅,因为多余的文本位将成为接口的参数。如果你们能指出我们与此设计有关的任何其他技术问题,我会很高兴,但这是我目前唯一关心的问题。

有什么办法可以去掉那个多余的模板参数?可能使用宏?

注意:有问题的方法必须返回一个实例。我知道如果method() 返回一个指针或引用,就没有问题。

【问题讨论】:

  • 您使用的成语称为Curiously Recurring Template Pattern。我想你可以用#define DERIVE_TEMPLATE_BASE(Derived, Base) class Derived : public Base&lt;Derived&gt; 之类的宏替换类声明,但它看起来非常难看,可能会让你的编辑器感到困惑。底线 - 是的,有一个冗余,但它是公认的和可识别的成语。
  • @gwiazdorrr:看起来还不错。如果这是一个公认的习语,那么我可以假设它对“界面”的用户来说不会太陌生,对吗?
  • 另外请注意,(至少在本例中),将调用设为虚拟是没有意义的,因为 CRTP 需要已知的大多数派生类型才能使用基本类型。
  • @MooingDuck:这是一个有趣的效果。析构函数呢?
  • 只要你不调用 Interface&lt;Implementation&gt; 的析构函数就没有关系。你不会,因为没有理由用std::vector&lt;Interface&lt;Implementation&gt;&gt; 代替std::vector&lt;Implementation&gt;

标签: c++ oop inheritance abstract-class


【解决方案1】:

Interface::method() 不能在不使用指针或引用的情况下返回 Interface 实例。返回非指针、非引用的Interface 实例需要实例化Interface 本身的实例,这是非法的,因为Interface 是抽象的。如果您希望基类返回一个对象实例,您必须使用以下方法之一:

指针:

class Interface
{
public:
  virtual Interface* method() = 0;
};

class Implementation : public Interface
{
public:
  virtual Interface* method() { /* ... */ }
};

参考:

class Interface
{
public:
  virtual Interface& method() = 0;
};

class Implementation : public Interface
{
public:
  virtual Interface& method() { /* ... */ }
};

模板参数:

template<type T>
class Interface
{
public:
  virtual T method() = 0;
};

class Implementation : public Interface<Implementation>
{
public:
  virtual Implementation method() { /* ... */ }
};

【讨论】:

    【解决方案2】:

    虽然你不能按 value 的原因返回,但返回指针或引用是完全可以的——这被称为“协变返回类型”,它是一种有效的虚函数形式覆盖:

    struct Base { virtual Base * foo(); }
    struct Derived : Base { virtual Derived * foo(); }
    

    关键是Derived::foo() 是真正的覆盖,而不是基本隐藏重载,因为Derived* 是指向Base 派生类的指针。这同样适用于引用。

    换句话说,如果您有一个Base * p,并且您调用p-&gt;foo(),您始终可以将结果视为指向Base 的指针(但如果您有其他信息,例如您的班级在事实上Derived,那么您可以使用该信息)。

    相反的组合顺序,即“逆变参数类型”,不允许作为 C++ 的一部分。

    【讨论】:

      猜你喜欢
      • 2016-09-21
      • 1970-01-01
      • 1970-01-01
      • 2017-12-01
      • 2013-10-12
      • 1970-01-01
      • 2010-12-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多