Возврат ссылки на производный класс из метода базового класса - PullRequest
3 голосов
/ 03 июня 2019

У меня есть задача реализовать простой генератор SVG.Мне нужно поддерживать круг, полилинию и текст.Все три имеют по крайней мере 4 общих метода: - SetStrokeColor - SetFillColor - SetStrokeWidth - ToString Одним из основных требований является поддержка цепочки , например: Polyline {}. SetStrokeColor ("white"). SetFillColor ("black") ...

Я решил реализовать элемент базового класса, от которого наследуются все остальные классы.Идея состоит в том, чтобы иметь класс Document, который содержит вектор всех элементов, добавленных в документ.Пример сигнатуры для базового метода:

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

Мои производные классы вызывают эти методы, но проблема в том, что методы возвращают ссылку на элемент базового класса, а не на производный класс.

** * * * * * * * * * * * * * * * * *. * * * *. * * *

Ответы [ 3 ]

4 голосов
/ 03 июня 2019

Если вы хотите поделиться реализациями и , сохраняющими информацию о типе, вам необходим 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);
}

Посмотреть в прямом эфире на Wandbox

3 голосов
/ 03 июня 2019

С ковариантными типами возврата вы можете

class Element {
  public:
    // ...

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

и в производных классах

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

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

, который позволяет обрабатывать Derived экземпляров как Derived экземпляров, когда статический тип равен Derived (например, внутри самого Derived). Когда статическим типом является Element, тип возвращаемого значения refToThis() тоже.

0 голосов
/ 03 июня 2019

Для сравнения:

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, так что да, вы можете смело делать это.

Edit:

Circle().SetStrokeWidth(16).SetCircleCenter({0, 0}). SetStrokeWidth возвращает ссылку на элемент, поэтому SetCircleCenter недоступен.

В этом случае у вас все же проблемы. Поскольку lubgr уже обозначено , вы можете решить эту проблему, переопределив кооперируемый тип возврата - и да, это будет означать, что вам придется переопределять каждую функцию отдельно. Кроме того, вы можете сохранить весь этот подход, используя 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...