返回派生类类型的基类中的方法

Method in base class that returns derived class type?

本文关键字:基类 方法 类型 派生 返回      更新时间:2023-10-16

我有一堆类,它们有一个共同的函数,除了它返回一个指向它们自己类型的指针。代码看起来是一样的,我想把它移动到一个抽象基类。但是,如何使从它继承的类返回自己的类型呢?

class base {
    base *foo() {
        // ...
    }
};

class derived : public base {
};
derived d;
d.foo(); // Should return derived* instead of base*

是否有一种方法来表达这在c++中?

是的,c++支持这个。它叫做协变返回类型。你只需要声明函数为virtual,并相应地声明返回类型。这就是它的全部。

struct base {
    virtual base *foo() {
        // ...
    }
};

struct derived : public base {
    virtual derived *foo() {
        // ...
    }
};
derived d;
base *base_ptr = d.foo();

现在你的评论扩展了原来的问题:

但实际上我的目标是不重复函数体,因为它是除了使用的类型之外,其他内容相同。

这是不可能的

有各种各样的技术可以促进的重复,但是你无法绕过这样一个事实:无论你做什么,你仍然必须自己创建函数体。

其中一种技术是使用宏,代价是混淆和宏带来的所有其他缺点;但是,宏仍然不会自动出现在类中。你必须把它放在那里。
// beware of macros!
#define FOO(T) virtual T *foo() { return new T; }
struct base {
    FOO(base)
    virtual ~base() {} // let's not forget the virtual destructor
};

struct derived : public base {
    FOO(derived)
};
一个类似的方法是使用模板: 方便函数体的重复。
template <class T>
T *ComplicatedFunctionReturningT()
{
    T *t;
    // ...
    // ...
    // ...
    return t;
}
struct base {
    virtual base *foo() {
        return ComplicatedFunctionReturningT<base>();
    }
    virtual ~base() {} // let's not forget the virtual destructor
};

struct derived : public base {
    virtual derived *foo() {
        return ComplicatedFunctionReturningT<derived>();
    }
};

模板比宏更安全。


另一种方法是使用模板方法设计模式。如果在每个类的函数体中重复了大量代码,请尝试在基类中尽可能多地移动,并将一小部分抽象部分放入要重写的私有函数中:
class base {
public:
    base *foo() { // no longer virtual
        // ...
        // ...
        base *ptr = fooImpl();
        // ...
        // ...
        return ptr;
    }
    virtual ~base() {} // let's not forget the virtual destructor
private:
    virtual base *fooImpl() = 0; // pure virtual and private
};
class derived1 : public base {
private:
    virtual derived1 *fooImpl() {
        return new derived1; // very simple body
    }
};
class derived2 : public base {
private:
    virtual derived2 *fooImpl() {
        return new derived2; // very simple body
    }
};

当然,只有在函数体非常复杂的情况下,才值得这么做。在极端情况下,一种完全不同的方法是使用一些外部工具或脚本生成c++代码。

最后,如果这确实是个问题,请从整体上重新考虑您的设计。也许事实证明您并不真正需要该函数,或者对于程序试图解决的实际问题,您并不需要OOP。

根据你对基督徒回答的评论,你可以实现一个模板助手方法来避免重复你将要使用的代码:

class base 
{
  protected:
    template<class T> T* fooInternal()
    {
        T* t = new T();
        // do stuff with t
        return t;
    }
  public:
    virtual base* foo() { return fooInternal<base>(); }
};

class derived : public base 
{
  public:
    virtual derived* foo() { return fooInternal<derived>(); }
};

一个选择是使用CRTP(奇怪的重复出现的模板模式)。

template<typename Derived>
struct base {
    Derived* foo() {
        // ...
        return static_cast<Derived*>(this);
    }
};
struct derived : base<derived> {
    // ...
};
derived d;
derived *derived_ptr = d.foo();

主要的好处是不需要宏或虚方法来获得所关心的类型。

如果要在基类中使用多个方法,那么最好定义一个helper方法。

template<typename Derived>
struct base {
    Derived* foo() {
        // ...
        return self();
    }
private:
    Derived* self() {
        return static_cast<Derived*>(this);
    }
};
例如,我在属性对象中使用了这种模式,其中我有一组基本的共享属性和几个派生的具有自己属性的属性对象。然后,我让基类方法返回对派生类的引用,这样我就可以在同一语句上链式设置多个属性。
    SomePropertyObject()
        .setBaseProperty("foo")
        .setDerivedProperty("bar");