Как правильно перегрузить операторы в абстрактных базовых классах? - PullRequest
7 голосов
/ 01 февраля 2011

Предположим, у меня есть абстрактный базовый класс, который просто определяет контейнер, для которого можно выполнить сложение:

class Base {
public:
    virtual ~Base() {}
    virtual Base operator+(const Base& rhs) =0;
};

Затем я хочу, чтобы подклассы Base обеспечивали фактическую операцию:

class Derived: public Base {
public:
    Base operator+(const Base& rhs) { // won't compile
        // actual implementation
    }
};

Вот моя проблема: оператор + () должен возвращать новый объект Base, но, будучи абстрактным, он не будет компилироваться.

Я пытался обойти это, используя фабрику для возврата ссылки на базовый объект, но затем в теле оператора я выполняю приведение типов, поскольку сложение имеет смысл только для производных объектов.

В любом случае мне кажется, что я кусаю свой хвост, есть ли правильное решение для этого?

ОБНОВЛЕНИЕ: Судя по ответам, похоже, я использую неправильныйшаблон.Я хочу отделить интерфейс от реализации, так что код библиотеки должен знать только интерфейс, а клиентский код обеспечивает реализацию.Я попытался сделать это, предоставив интерфейс как абстрактный базовый класс, а реализацию - как подклассы.

ОБНОВЛЕНИЕ 2: Мой вопрос был на самом деле 2 вопроса, конкретный (о перегрузке операторов в абстрактных классах) и другой о моем намерении (как я могу позволить клиенту настроить реализацию).На первое ответили: нет.Для последнего кажется, что шаблон класса интерфейса, который я использую, на самом деле является хорошим для решения этой проблемы (согласно Гриффитсу и Рэдфорду ), просто я не должен связываться с перегруженными операторами.

Ответы [ 6 ]

8 голосов
/ 01 февраля 2011

Лучше не надо.

operator+ возвращает значение, и вы не можете вернуть значение абстрактного типа по определению. Перегружайте операторы только для конкретных типов и избегайте наследования от конкретных типов, чтобы предотвратить «нарезку перегруженным оператором».

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

Если у вас есть правильный способ выполнить «добавление» с помощью двух ссылок на базовый класс и создать новый объект, вам придется возвращаться с помощью смарт-объекта с указателем, ссылкой или переносом указателя. Поскольку вы не можете сохранить обычную семантику +, я бы порекомендовал использовать именованную функцию, например, Add() вместо создания operator+ с "удивительным" синтаксисом.

2 голосов
/ 01 февраля 2011

Обычное решение этой проблемы - модель алгебраической иерархии Джима Коплина. Смотрите следующее:

Оригинальная статья Коплина (особенно Алгебраическая иерархия )

Wikibooks

Чтобы уточнить, вам, по сути, нужен конкретный класс-обертка, который содержит полиморфный указатель на фактический объект производного класса. Определите рассматриваемые операторы для пересылки в базовое представление, сохраняя семантику значений (а не семантику объектов), которую вы ищете. Убедитесь, что класс-оболочка использует идиому RAII , чтобы не пропускать память при каждом временном объекте.

1 голос
/ 01 февраля 2011
template<class C>
class Base {
public:
    virtual ~Base() {}
    virtual C operator+(const C& rhs) =0;
};

class Derived: public Base<Derived> {
public:

может работать (за исключением неполной проблемы класса).

0 голосов
/ 05 марта 2012

использовать динамический тип и базовый тип. в базовом классе перечислить производный класс невозможно, так как перегрузка оператора должна быть статической все время. Наиболее удобным вариантом будет использование «динамического» типа в аргументах перегрузки. Ниже приведен пример.

public class ModelBase
{
    public abstract static string operator +(ModelBase obj1, dynamic obj2)
    {
        string strValue = "";
        dynamic obj = obj1;
        strValue = obj.CustomerID + " : " + obj2.CustomerName;
        return strValue;
    }
}
0 голосов
/ 02 февраля 2011

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

Используйте pimpl idiom :

struct Base {
  virtual ~Base() = 0;
  virtual std::auto_ptr<Base> clone() = 0;
  virtual void add(Base const &x) = 0;  // implements +=
};

struct UserInterface {
  UserInterface() : _pimpl(SomeFactory()) {}
  UserInterface(UserInterface const &x) : _pimpl(x._pimpl->clone()) {}

  UserInterface& operator=(UserInterface x) {
    swap(*this, x);
    return *this;
  }

  UserInterface& operator+=(UserInterface const &x) {
    _pimpl->add(*x._pimpl);
  }

  friend void swap(UserInterface &a, UserInterface &b) {
    using std::swap;
    swap(a._pimpl, b._pimpl);
  }

private:
  std::auto_ptr<Base> _pimpl;
};

UserInterface operator+(UserInterface a, UserInterface const &b) {
  a += b;
  return a;
}

Чтобы реально реализовать Base :: add, вам понадобится либо 1) двойная диспетчеризация и работа с результирующим взрывом перегрузки, 2) требовать, чтобы только один производный класс base использовался для выполнения программы (все еще используя pimpl в качестве межсетевого экрана компиляции, напримердля замены общих библиотек), или 3) требуют, чтобы производные классы знали, как обращаться с общей базой, или вызовут исключение.

0 голосов
/ 01 февраля 2011

Base будучи абстрактным, вы не можете создать его экземпляр, поэтому возврат по значению исключен.Это означает, что вам придется вернуться по указателю или по ссылке.Возврат по ссылке опасен, поскольку operator+ может возвращать временное значение.

Это по указателю, но это было бы очень странно.И весь вопрос в том, как освободить указатель, когда он вам больше не нужен.Использование умного указателя может быть решением этой проблемы, но у вас все еще есть проблема "operator+ возвращает указатель".

Итак, чего вы пытаетесь достичь?Что означает Base?Имеет ли смысл добавлять два экземпляра Base подклассов?

...