从基类方法返回对派生类的引用

时间:2019-06-03 08:00:29

标签: c++ inheritance

我有一个任务来实现一个简单的SVG生成器。我需要支持Circle,Polyline和Text。这三种方法至少有4种常用方法: -SetStrokeColor -SetFillColor -SetStrokeWidth -ToString 主要要求之一是支持链接,例如: 折线{} .SetStrokeColor(“ white”)。SetFillColor(“ black”)...

我决定实现一个基类Element,所有其他类均从该基类继承。想法是拥有一个Document类,其中包含添加到文档中的所有元素的向量。 基本方法的样本签名:

// source of trouble
Element &SetStrokeColor(const Color &color) {
    ...
    return *this;
}

我的派生类确实调用了这些方法,但是麻烦的是,这些方法返回对基类Element的引用,而不是派生类。

我的问题是,是否可以全部用c ++来实现??

Further discussion here

3 个答案:

答案 0 :(得分:4)

如果要共享实现保留类型信息,则需要CRTP:

struct ElementBase { };

template <class Concrete>
struct Element : ElementBase {

    Concrete &setStrokeWidth(int width) {
        // Actual implementation...
        (void) width;

        return cthis();
    }

private:
    friend Concrete;
    Element() = default;

    Concrete &cthis() { return static_cast<Concrete &>(*this); }
    Concrete &cthis() const { return static_cast<Concrete const &>(*this); }
};

struct Circle : Element<Circle> {
    Circle &SetCircleCenter(int x, int y) {
        // Actual implementation...
        (void) x;
        (void) y;

        return *this;
    }
};

int main() {
    Circle c;
    c.setStrokeWidth(4).SetCircleCenter(0, 0);
}

See it live on Wandbox

答案 1 :(得分:3)

使用协变返回类型,您可以

class Element {
  public:
    // ...

    virtual Element& refToThis() { return *this; };
};

以及派生类

class Derived : public Element {
  public:
    // ...

    Derived& refToThis() override { return *this; };
};

,当静态类型为Derived时(例如,在Derived本身中),您可以将Derived实例作为Derived实例处理。当静态类型为Element时,refToThis()的返回类型也为

答案 2 :(得分:0)

为进行比较:

class Base {};
class Derived : public Base {};

Derived d;
Base* p = &d; // points to a Base that in reality is a derived
Base& b =  d; // in this respect, references do not differ...

// and you can get the original type back:
auto dr = static_cast<Derived&>(b);  // if you know 100% for sure that b is a Derived
auto dp = dynamic_cast<Derived*>(p); // if p might point to another type; you get
                                     // a null pointer back, if it does

返回指针或对this / *this的引用绝对没有区别,所以是的,您可以放心地这样做。

编辑:

  

Circle().SetStrokeWidth(16).SetCircleCenter({0, 0})。 SetStrokeWidth返回对Element的引用,因此SetCircleCenter不可用。

在这种情况下,您会遇到麻烦。就像lubgr denoted一样,您可以通过使用协变量返回类型覆盖来解决此问题–是的,这意味着您必须分别覆盖每个函数。另外,您可以使用CRTP保存所有这种方法:

template <typename T>
class SomeAppropriateName : public Element
{
public:
    T& setStrokeWidth(unsigned int width)
    {
        Element::setStrokeWidth(width);
        return static_cast<T&>(*this);
    }
};

class Circle : public SomeAppropriateName<Circle>
{
    // the overrides needed are inherited...
};

对于这种方法,Element类中的原始函数必须是非虚拟的(实际上是一个优势,因为虚拟函数的调用成本更高...);在模板中,参数(T,即Circle尚未完全定义(使用CRTP模式),编译器将无法检查返回类型是否确实是协变的。所以实际上,我们只是在遮蔽基类的功能。

与覆盖相比,这仍然没有缺点:协变量返回类型仅在无论如何直接在对象上直接调用(虚拟)函数时才可用:

Circle c;
c.setStrokeWidth(7).setCenter();

Element& e = c;
e.setStrokeWidth(7).setCenter(); // fails, even if you implement co-variant override directly