Каков правильный способ перегрузки операторов в абстрактных базовых классах?
Предположим, у меня есть абстрактный базовый класс, который просто определяет контейнер, на котором дополнительно могут быть выполнены:
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 документа (особенно Алгебраические Иерархии)
чтобы разработать бит, вам по существу нужен конкретный класс-оболочка, который содержит полиморфный указатель на фактический объект производного класса. Определите операторы, о которых идет речь, для пересылки в базовое представление сохранение семантики значений (а не семантики объектов), которую вы ищете. Убедитесь, что класс-оболочка использует РАИИ идиома, чтобы вы не пропускали память с каждым временным объектом.
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;
}
}