Подтвердить что ты не робот

Цепочка метода + наследование плохо сочетается?

Рассмотрим:

// member data omitted for brevity

// assume that "setAngle" needs to be implemented separately
// in Label and Image, and that Button does need to inherit
// Label, rather than, say, contain one (etc)

struct Widget {
    Widget& move(Point newPos) { pos = newPos; return *this; }
};

struct Label : Widget {
    Label& setText(string const& newText) { text = newText; return *this; }
    Label& setAngle(double newAngle) { angle = newAngle; return *this; }
};

struct Button : Label {
    Button& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
        return *this;
    }
};

int main() {
    Button btn;

    // oops: Widget::setText doesn't exist
    btn.move(Point(0,0)).setText("Hey");

    // oops: calling Label::setAngle rather than Button::setAngle
    btn.setText("Boo").setAngle(.5); 
}

Любые методы решения этих проблем?

Пример: использование магии шаблона для создания кнопки:: move return Button & или что-то в этом роде.

edit. Стало ясно, что вторая проблема решена путем создания виртуального приложения setAngle.

Но первая проблема остается нерешенной разумным образом!

изменить. Ну, я думаю, это невозможно сделать правильно на С++. Спасибо за все усилия.

4b9b3361

Ответ 1

Для второй проблемы, делая setAngle virtual, должен сделать трюк.

Для первого нет простых решений. Widget:: move возвращает виджет, который не имеет метода setText. Вы можете сделать чистый виртуальный метод setText, но это будет довольно уродливое решение. Вы можете перегрузить move() в классе кнопок, но это будет больно для поддержания. Наконец, вы могли бы что-то сделать с помощью шаблонов. Возможно, что-то вроде этого:

// Define a move helper function
template <typename T>
T& move(T& obj, Point& p){ return obj.move(p); };

// And the problematic line in your code would then look like this:
move(btn, Point(0,0)).setText("Hey");

Я позволю вам решить, какое решение является самым чистым. Но есть ли какая-то особая причина, по которой вам нужно связать эти методы?

Ответ 2

Вы можете расширить CRTP, чтобы справиться с этим. решение монжардина идет в правильном направлении. Все, что вам нужно, это реализация по умолчанию Label для использования в качестве класса листа.

#include <iostream>

template <typename Q, typename T>
struct Default {
    typedef Q type;
};

template <typename T>
struct Default<void, T> {
    typedef T type;
};

template <typename T>
void show(char const* action) {
    std::cout << typeid(T).name() << ": " << action << std::endl;
}

template <typename T>
struct Widget {
    typedef typename Default<T, Widget<void> >::type type;
    type& move() {
        show<type>("move");
        return static_cast<type&>(*this);
    }
};

template <typename T = void>
struct Label : Widget<Label<T> > {
    typedef typename Default<T, Widget<Label<T> > >::type type;
    type& set_text() {
        show<type>("set_text");
        return static_cast<type&>(*this);
    }
};

template <typename T = void>
struct Button : Label<Button<T> > {
    typedef typename Default<T, Label<Button<T> > >::type type;
    type& push() {
        show<type>("push");
        return static_cast<type&>(*this);
    }
};

int main() {
    Label<> lbl;
    Button<> btt;

    lbl.move().set_text();
    btt.move().set_text().push();
}

Тем не менее, подумайте, стоит ли такое усилие небольшому добавленному бонусу синтаксиса. Рассмотрим альтернативные решения.

Ответ 3

Простой, но раздражающий способ решения вашей проблемы состоит в том, чтобы переопределить все ваши общедоступные методы в ваших подклассах. Это не решает проблему с полиморфизмом (например, если вы отбрасываете от Label to Widget), что может быть или не быть серьезной проблемой.

struct Widget {
    Widget& move(Point newPos) { pos = newPos; return *this; }
};

struct Label : Widget {
    Label& setText(string const& newText) { text = newText; return *this; }
    Label& setAngle(double newAngle) { angle = newAngle; return *this; }
    Label& move(Point newPos) { Widget::move(newPos); return *this; }
};

struct Button : Label {
    Button& setText(string const& newText) { Label::setText(newText); return *this; }
    Button& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
        return *this;
    }
    Button& move(Point newPos) { Label::move(newPos); return *this; }
};

Обязательно включите в свою документацию это должно быть сделано для цепочки для работы.

Но на самом деле, зачем беспокоиться о цепочке методов? Много раз эти функции будут называться менее тривиально и потребуются более длинные линии. Это действительно ухудшит читаемость. Одно действие на линию - это общее правило, когда дело доходит до ++ и --.

Ответ 4

Действительно ли Button a Label? Вы, кажется, нарушаете принцип замещения Лискова. Возможно, вы должны рассмотреть шаблон Decorator, чтобы добавить поведение к виджетам.

Если вы настаиваете на структуре как есть, вы можете решить свою проблему следующим образом:

struct Widget {
    Widget& move(Point newPos) { pos = newPos; return *this; }
    virtual ~Widget();  // defined out-of-line to guarantee vtable
};

struct Label : Widget {
    Label& setText(string const& newText) { text = newText; return *this; }
    virtual Label& setAngle(double newAngle) { angle = newAngle; return *this; }
};

struct Button : Label {
    virtual Label& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
        return *this;
    }
};

int main() {
    Button btn;

    // Make calls in order from most-specific to least-specific classes
    btn.setText("Hey").move(Point(0,0));

    // If you want polymorphic behavior, use virtual functions.
    // Anything that is allowed to be overridden in subclasses should
    // be virtual.
    btn.setText("Boo").setAngle(.5); 
}

Ответ 5

С++ поддерживает ковариантность возвращаемого значения для виртуальных методов. Таким образом, вы можете получить что-то вроде того, что хотите, с небольшой работой:

#include <string>
using std::string;

// member data omitted for brevity

// assume that "setAngle" needs to be implemented separately
// in Label and Image, and that Button does need to inherit
// Label, rather than, say, contain one (etc)


struct Point
{
    Point() : x(0), y(0) {};
    Point( int x1, int y1) : x( x1), y( y1) {};

    int x;
    int y;
};

struct Widget {
    virtual Widget& move(Point newPos) { pos = newPos; return *this; }
    virtual ~Widget() {};

    Point pos;
};

struct Label : Widget {
    virtual ~Label() {};
    virtual Label& move( Point newPos) { Widget::move( newPos); return *this; }

    // made settext() virtual, as it seems like something 
    // you might want to be able to override
    // even though you aren't just yet
    virtual Label& setText(string const& newText) { text = newText; return *this; }
    virtual Label& setAngle(double newAngle) { angle = newAngle; return *this; }

    string text;
    double angle;
};

struct Button : Label {
    virtual ~Button() {};
    virtual Button& move( Point newPos) { Label::move( newPos); return *this; }
    virtual Button& setAngle(double newAngle) {
        //backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
        return *this;
    }
};

int main()
{
    Button btn;

    // this works now
    btn.move(Point(0,0)).setText("Hey");

    // this works now, too
    btn.setText("Boo").setAngle(.5); 

  return 0;
}

Обратите внимание, что вы должны использовать виртуальные методы для выполнения чего-то подобного. Если они не являются виртуальными методами, то методы переопределения приведут к скрытию имен, и вызванный метод будет зависеть от статического типа переменной, указателя или ссылки, поэтому он может быть не правильным методом, если базовый указатель или используется ссылка.

Ответ 6

[напыщенная]

Да. Выйдите из этого метода, связав бизнес и просто вызовите функции в строке.

Серьезно, вы платите цену за разрешение этого синтаксиса, и я не получаю преимущества, которые он предлагает.

[/напыщенная]

Ответ 7

Не с С++.

С++ не поддерживает дисперсию в возвращаемых типах, поэтому нет способа изменить статический тип ссылки, возвращаемой из Widget.move(), чтобы быть более конкретным, чем Widget & даже если вы его переопределите.

С++ должен уметь проверять вещи во время компиляции, поэтому вы не можете использовать тот факт, что то, что действительно возвращается из перемещения, - это кнопка.

В лучшем случае вы можете выполнить кастинг времени исполнения, но это не будет выглядеть красиво. Просто отдельные вызовы.

Изменить: Да, я хорошо знаю, что стандарт С++ говорит, что ковариантность возвращаемого значения является законной. Однако в то время, когда я преподавал и практиковал С++, некоторые компиляторы (например, VС++) не использовали. Следовательно, для переносимости мы рекомендовали против него. Вполне возможно, что текущие компиляторы не имеют проблемы с этим, наконец.

Ответ 8

Некоторое время я думал, что можно было бы перегрузить несколько необычные operator->() для методов цепочки вместо ., но это заглохло, потому что кажется, что компилятору нужен идентификатор справа от -> принадлежать статическому типу выражения слева. Достаточно справедливо.

Цепочка метода бедных людей

Отступив на мгновение, точка цепочки методов состоит в том, чтобы избежать многократного повторного ввода длинных имен объектов. Я предлагаю следующий быстрый и грязный подход:

Вместо "формы длинной формы":

btn.move(Point(0,0)); btn.setText("Hey");

Вы можете написать:

{Button& _=btn; _.move(Point(0,0)); _.setText("Hey");}

Нет, это не так просто, как реальная цепочка с ., но она сохранит некоторую типизацию, когда есть множество параметров для установки, и она имеет то преимущество, что для нее требуется никаких изменений кода к вашим существующим классам. Поскольку вы перекрываете всю группу вызовов методов в {}, чтобы ограничить область действия ссылки, вы всегда можете использовать один и тот же короткий идентификатор (например, _ или x) для обозначения имени конкретного объекта, что потенциально увеличивает читаемость, Наконец, у компилятора не будет проблем с оптимизацией _.

Ответ 9

Я бы отказался от цепочки. Во-первых, на самом деле это невозможно сделать, не делая довольно неприятных хаков. Но самая большая проблема заключается в том, что сложнее читать и поддерживать код, и вы, скорее всего, окажетесь перед людьми, злоупотребляющими им, создавая одну гигантскую линию кода, чтобы делать несколько вещей (вспомните об алгебре средней школы и этих гигантских линиях добавок, вычитаний и умножений, которые вы, как всегда, оказываетесь в конечном итоге, в какой-то момент, это то, что люди будут делать, если вы позволите им).

Другая проблема заключается в том, что, поскольку большинство ваших функций в системе будут возвращать ссылку на себя, это будет логично, что все они должны. Когда (а не если) вы, наконец, начинаете внедрять функции, которые также должны возвращать значения (не только аксессоры, но и некоторые мутаторы, а также другие общие функции), вы столкнетесь с дилеммой, либо нарушите свое соглашение (которое будет снежок, делая непонятным, как вещи должны быть реализованы для других будущих функций) или вынуждены начинать возвращать значения через параметры (что, я уверен, вы будете ненавидеть, как это делают многие другие программисты, которых я знаю).

Ответ 10

Я думаю (я его не тестировал) это будет делать с помощью шаблонов:

template<class T> struct TWidget {
    T& move(Point newPos) { pos = newPos; return (T&)*this; }
};

template<class T> struct TLabel : TWidget<T> { ... }

struct Label : TLabel<Label> { ... }

struct Button : TLabel<Button> { ... }

Примечания:

  • Любой/каждый базовый класс должен быть шаблоном с отдельным классом листьев, отличным от шаблона, в верхней части (сравните классы LabelT и Label).
  • Приведение может быть dynamic_cast, если хотите.
  • Вместо "return *this" базовый класс может содержать T& как элемент данных (производный класс передаст this в конструктор базового класса), который будет дополнительным элементом данных, но который избегает броска, и я думаю, что он может разрешить композицию вместо или так же, как и наследование.

Ответ 11

Это компилируется на gcc 4.3.2 и является своего рода шаблон mixin.

#include <string>

using namespace std;

struct Point {
    Point() : x(0), y(0) {}
    Point(int x, int y) : x(x), y(y) {}

    int x, y;
};

template <typename T>
struct Widget {
    T& move(Point newPos) {
        pos = newPos;
        return *reinterpret_cast<T *> (this);
    }

    Point pos;
};

template <typename T>
struct Label : Widget<Label<T> > {
    T& setText(string const& newText) {
        text = newText;
        return *reinterpret_cast<T *> (this);
    }
    T& setAngle(double newAngle) {
        angle = newAngle;
        return *reinterpret_cast<T *> (this);
    }

    string text;
    double angle;
};

struct Button : Label<Button> {
    Button& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label<Button>::setAngle(newAngle);
        return *this;
    }

    Label<Button> backgroundImage;
};

int main() {
    Button btn;

    // oops: Widget::setText doesn't exist
    btn.move(Point(0,0)).setText("Hey");

    // oops: calling Label::setAngle rather than Button::setAngle
    btn.setText("Boo").setAngle(0.0); 
}

Ответ 12

Ну, вы знаете, что это Button, чтобы вы могли вернуть возвращенный Widget& как Button& и продолжать. Это выглядит немного уродливым, хотя.

Другой довольно неприятный вариант - создать оболочку в классе Button для функции Widget::move (и друзей). Вероятно, не стоит пытаться обернуть все, если у вас есть больше, чем несколько функций.

Ответ 13

Действительно ли setText() и setAngle() действительно должны возвращать свои собственные типы в каждом классе? Если вы установите их для возврата Widget &, то вы можете просто использовать виртуальные функции следующим образом:

struct Widget {
    Widget& move(Point newPos) { pos = newPos; return *this; }
    virtual Widget& setText(string const& newText) = 0;
    virtual Widget& setAngle(double newAngle) = 0;
};

struct Label : Widget {
    virtual Widget& setText(string const& newText) { text = newText; return *this; }
    virtual Widget& setAngle(double newAngle) { angle = newAngle; return *this; }
};

struct Button : Label {
    virtual Widget& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
        return *this;
    }
};

Обратите внимание, что даже если тип возврата - Widget &, функции Button или Label будут по-прежнему быть теми, которые называются.

Ответ 14

Другие люди столкнулись с проблемами дизайна. Вы можете решить проблему (хотя и довольно грубо) с помощью поддержки С++ для ковариантных типов возврата.

struct Widget {
    virtual Widget& move(Point newPos) { pos = newPos; return *this; }
};

struct Label : Widget {
    Label& move(Point newPos) { pos = newPos; return *this; }
    Label& setText(string const& newText) { text = newText; return *this; }
    Label& setAngle(double newAngle) { angle = newAngle; return *this; }
};

struct Button : Label {
    Button& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
        return *this;
    }
};

int main() {
    Button btn;

    // oops: Widget::setText doesn't exist
    btn.move(Point(0,0)).setText("Hey");

    // oops: calling Label::setAngle rather than Button::setAngle
    btn.setText("Boo").setAngle(.5); 
}

На самом деле, хотя методы цепочки, как вы делаете для странного кода, и больны читабельностью, а не помогают. Если вы убили ссылку на возврат к самому мужу, ваш код станет следующим:

struct Widget {
    void move(Point newPos) { pos = newPos; }
};

struct Label : Widget {
    void setText(string const& newText) { text = newText; }
    void setAngle(double newAngle) { angle = newAngle; }
};

struct Button : Label {
    void setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
    }
};

int main() {
    Button btn;

    // oops: Widget::setText doesn't exist
    btn.move(Point(0,0));
    btn.setText("Hey");

    // oops: calling Label::setAngle rather than Button::setAngle
    btn.setText("Boo");
    btn.setAngle(.5); 
}

Ответ 15

Действительно, цепочка методов является плохой идеей из-за плохой безопасности исключений. Вам это действительно нужно?