抽象基类,用于派生类的函数返回类型为派生类本身。

3

我希望强制要求所有派生自基类的类使用特定的API,通常你需要使用抽象基类并定义纯虚函数来实现。然而,当涉及到需要返回派生类型的函数时,该如何处理?如何强制要求使用此类型的函数?

struct base
{
    virtual base func() = 0;
};

struct deriv1 : base
{
    deriv1 func();
};

struct deriv2 : base
{
    deriv2 func();
};

这个例子将会出现一个错误,类似于“成员函数的无效抽象返回类型”。我看过一些答案,建议返回指针,但我不想为此涉足动态内存,并且跟踪所有已分配的指针将是一种特别的痛苦。有什么好的想法吗?


我不确定你想做什么。为什么要通过值返回类型?你能给这些函数起一个现实的名字吗?通常在克隆时,会返回派生类型的引用或指针。如果内存管理是一个问题,可以返回智能指针(例如Google的shared_ptr)。 - Werner Erasmus
基本上,我从不想使用基类或进行任何多态性操作。我只想创建一个API,以便从基类派生的任何类都可以保证在相同情况下正常工作。基本上是一种说法:“如果你想在代码中用deriv2代替deriv1,你需要实现这些函数”。这是唯一的用例。 - Ned Bingham
那个特定的用例不值得使用智能指针和动态内存所带来的麻烦或性能损失。 - Ned Bingham
什么性能损失?使用分析器来测量性能,而不是假设。 - Neil Kirk
http://stackoverflow.com/questions/1176298/best-practices-of-dynamic-vs-static-memory-in-terms-of-cleanliness-and-speed - Ned Bingham
你考虑过将这个类设计为泛型吗? - Kyle Delaney
3个回答

2
当虚函数返回一个指向类的指针或引用时,继承自基类并覆盖该函数的类被允许将返回类型更改为指向原始返回类型派生类的指针或引用。
由于抽象类无法单独创建,因此无法按值返回基类。

http://en.wikipedia.org/wiki/Covariant_return_type

当使用虚函数和基类时,通常需要使用动态分配来创建对象。我建议您研究智能指针以帮助管理内存。

1
在你的例子中,func不会是“相同的函数”,因此deriv1deriv2变体不会有不同的虚函数。
不幸的是,除了返回指针之外,没有其他选择-它不必是指向动态分配内存的指针(例如,您可以返回指向thisstatic deriv2 anObject; 的指针-但它需要是指向base的指针。[或引用,但同样的问题适用]。
这样做的主要原因(除了“函数不能仅基于返回类型进行区分”之外)是,如果您有一些通用代码,类似于这样:
vector<base*> v;
... stuff a bunch of `dervi1` or `deriv2` objects into v. 
for(i : v)
{
    base b = i->func();
}

现在,你要么已经把你的deriv1或者deriv2切成了一个base的大小,要么你已经复制了一个比base大的对象到一个base大小的对象里——这两种情况都没有任何好处。[我假设在实际使用中,deriv1deriv2base不同之处不仅仅是对象名称——否则就毫无意义。当然,deriv1deriv2继承自base]。
换句话说,你不能通过=来复制一个未知类型的对象。如果你必须知道它返回的类型,那么虚函数就没有任何意义。

0
基本上是一种说法,“如果你想在代码中用deriv2替换deriv1,你需要实现这些函数。”从你上面的引用来看,似乎你想要这样的东西:
#include <memory> //for unique_ptr
#include <iostream>

struct Base
{
  virtual void doX() = 0;
  virtual void doY() = 0;
  virtual ~Base(){}
};

struct D1 : Base
{
  virtual void doX()
  {
    std::cout << "D1::doX()" << std::endl;  
  }
  virtual void doY()
  {
    std::cout << "D1::doY()" << std::endl;  
  }
};

struct D2 : D1
{
  virtual void doX()
  {
    std::cout << "D2::doX()" << std::endl;  
  }
  virtual void doY()
  {
    std::cout << "D2::doY()" << std::endl;  
  }
};

//From this point on you can do various things:

void driver()
{
  Base* base = new D1;//
  base->doX(); //Calls D1::doX()
  D1* d1 = new D2;
  d1->doX(); //Calls D2::doX()
}
// or...
void driver( Base* base )
{
  //A way to replace Base with D1 with D2 depending
  // on how driver was called.
}

//Finally, maybe you want a factory to create the correct
// derived type based on which factory was instantiated.

// Creates family of products, each item representing the base
// in it's hierarchy - only one shown here...
struct AbstractFactory
{
  virtual std::unique_ptr<Base> create() const = 0;
  protected:
    virtual ~AbstractFactory(){}
};

struct D1Factory : AbstractFactory
{
  //Doesn't matter if <Derived> is returned, because it adheres
  // to interface of Base (isA base), and correct functions are
  // found polymorphically 
  virtual std::unique_ptr<Base> create() const
  {
    return std::unique_ptr<Base>( new D1 ); 
  }
};
struct D2Factory : AbstractFactory
{
  //Doesn't matter if <Derived> is returned, because it adheres
  // to interface of Base (isA base), and correct functions are
  // found polymorphically 
  virtual std::unique_ptr<Base> create() const
  {
    return std::unique_ptr<Base>( new D2 ); 
  }
};

void driver( const AbstractFactory& factory )
{
  std::unique_ptr<Base> base( factory.create() );
  base->doX();
  base->doY();
  //Memory deallocated automagically...
}

int main()
{
  driver( D1Factory() );
  driver( D2Factory() );
}

您会发现这个引用是正确的。从驱动程序的角度来看,D2可以无缝地替换D1...


是的,但我提出的问题不是如何处理普通函数调用,而是处理返回类型等于派生类的对象的函数调用。请参见我的示例中的返回类型。 - Ned Bingham
我的观点是:你为什么需要这个?给一个具体的例子,用真实的名称。我认为你的困惑来自于GC语言,事实上这些语言返回后来被垃圾回收的引用。在C++中,您可以通过使用堆栈(用智能指针包装资源)来实现这一点。据我所知,没有一种语言有切片 - 即当派生类型被切片时,它失去了其虚拟部分,并成为基类。对于引用和指针,这是可以接受的,因为它们不能被切片。 - Werner Erasmus
@WernerErasmus 一个通用的3D向量旋转类,从中派生出两个类,AngleAxisRotator和QuaternionRotator。所讨论的函数将是一个组合两个旋转的函数。 - Varad Mahashabde

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