Каков правильный способ перегрузки операторов в абстрактных базовых классах?

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

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

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

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

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

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

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

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

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

6 ответов


лучше всего не.

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

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

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


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

Coplein документа (особенно Алгебраические Иерархии)

Wikibooks

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


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

class Derived: public Base<Derived> {
public:

может работать (исключая проблему класса imcomplete).


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

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

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


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

использовать pimpl идиома:

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) требовать, чтобы только один производный класс базы использовался для выполнения программы (все еще используя pimpl в качестве брандмауэра компиляции, например, для замены общих библиотек), или 3) требовать, чтобы производные классы знали, как обращаться с общей базой или будут выдавать исключение.


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

public class ModelBase
{
    public abstract static string operator +(ModelBase obj1, dynamic obj2)
    {
        string strValue = "";
        dynamic obj = obj1;
        strValue = obj.CustomerID + " : " + obj2.CustomerName;
        return strValue;
    }
}