Перегруженный метод С++ в производном классе

У меня следующий вопрос:

Предположим, базовый класс A с методом:

A& operator+(A& a) {...}

У меня также есть производный класс B, который перегружает (или, по крайней мере, должен) этот метод:

A& operator+(B& b) {...}

Проблема в том, что если я хочу вызвать что-то вроде: b + a (где b имеет тип B, а a - тип A), я получаю ошибку компиляции. (ошибка C2679: двоичный «+»: не найден оператор, который принимает правый операнд типа «A» (или нет приемлемого преобразования)).

Разве это не должно вызывать метод базового класса? (похоже, что он переопределяет метод..) Если нет, то почему? Есть ли способ исправить это (не говорите мне перегружать метод в B с помощью A&)

Извините, я не привожу примеры в форматированном тексте, но я не знаю, как его форматировать.

Заранее спасибо!

PS Я использую бета-версию Visual Studio 2010.


person George    schedule 24.01.2010    source источник


Ответы (4)


Нет, он не будет вызывать функцию базового класса. Класс B имеет operator+, он не принимает правильный параметр, конец истории.

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

A operator+(const A &lhs, const A &rhs) { ... }
B operator+(const B &lhs, const B &rhs) { ... }

Тогда b + a вызовут первого оператора, как и a + b. b + b вызовет второй.

В качестве альтернативы вы можете «скрыть» реализацию базового класса, поместив это в класс B:

using A::operator+;

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

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

person Steve Jessop    schedule 24.01.2010
comment
Не волнуйся, я бы не стал этого делать, если бы мне не нужно было. (я возвращаю ссылку на новый выделенный объект). Спасибо, но мне нужно использовать методы класса, чтобы добиться того, чего я хочу. - person George; 24.01.2010
comment
Если вы возвращаете ссылку на вновь выделенный объект, почти наверняка лучше написать функцию, а не вызывать operator+, потому что вы не можете соответствовать обычной семантике для оператора +. С вашим кодом следующая вся утечка памяти: A c = a + b;, a = a + b;, A &d = a + b + b;. Перегруженный оператор+ должен вести себя как сложение, иначе зачем перегружать? - person Steve Jessop; 24.01.2010
comment
Не могли бы вы рассказать, почему вам нужно использовать функцию-член? Насколько мне известно, в C++ нет ничего, что могла бы сделать функция-член, чего не могла бы сделать свободная функция, кроме очевидного сокращения this вместо указания объекта. - person Steve Jessop; 24.01.2010
comment
Я понимаю, зачем вам нужны операторы, но не понимаю, почему они должны быть функциями-членами. Если вы собираетесь как-то реализовать сборщик мусора, то можно избежать утечек памяти. - person Steve Jessop; 24.01.2010
comment
Ну, ты прав, может быть, это и не нужно, это правда. Но что, если я хочу иметь доступ к переменным и при этом использовать инкапсуляцию? Кстати, я новичок в С++, и это мой первый проект. Так что простите меня, если я где-то ошибся. - person George; 24.01.2010
comment
+1 за четкий ответ. Конечно, это путь. Я не вижу, как виртуальные функции принесут пользу, и наверняка дублирование кода, найденного в B, тоже нехорошо. - person Johannes Schaub - litb; 24.01.2010
comment
@Джордж. Сделайте бесплатную функцию другом класса и объявите/определите ее в том же заголовочном/исходном файле. Это не нарушает инкапсуляцию, поскольку оператор является частью открытого интерфейса класса. Находится ли код для этого в фигурных скобках определения класса, не имеет значения, хотя на самом деле для этого есть синтаксис, если вам от этого станет лучше ;-). Если вы не можете использовать друга, напишите свободный оператор, который вызывает общедоступную функцию добавления члена. Тогда у вас будет симметричная обработка преобразования, которую вы не можете получить от оператора функции-члена. - person Steve Jessop; 24.01.2010
comment
Синтаксис, кстати, class A { public: friend A operator+(const A &lhs, const A &rhs) { ... } }; - person Steve Jessop; 24.01.2010
comment
Поскольку + должен быть коммутативным (a+b == b+1), реализуйте функцию operator+, которая затем вызывает функцию-член: A operator+(const B &lhs, const A &rhs) { return rhs + lhs}. (или более явный rhs.plus(lhs)). - person Goswin von Brederlow; 22.11.2014

Проблема называется скрытием — функция-член в производном классе скрывает функции с тем же именем в базовом классе. В этом случае вы не можете получить доступ к A::operator+(A&), потому что он скрыт B::operator+. Способ исправить это — определить B::operator+(A&) и, возможно, заставить его вызывать функцию базового класса.

Изменить: есть раздел в C++ FAQ Lite, в котором более подробно рассматривается эта проблема и предлагается другое возможное решение, а именно ключевое слово using.

person Mark Ransom    schedule 24.01.2010
comment
Что ж, в этом есть смысл, даже если, по моему мнению, этого не должно происходить. Я сделаю, как вы сказали, и переопределю его в производном классе. Спасибо - person George; 24.01.2010
comment
Мне это тоже никогда не нравилось, но так работает C++, к лучшему или к худшему. Это один из тех неясных уголков языка, который в конце концов кусает всех. У авторов компилятора, должно быть, была очень веская причина хотеть этого. - person Mark Ransom; 24.01.2010

Проблема в том, что вы определяете оператор member, поэтому при вызове как b + a он приводит к b.operator+( a ), которого не существует.

Accepted practice is to define free operators that themselves would call [virtual] members on the arguments.

Редактировать:

Standard example of what I'm talking about is adapting a class hierarchy for output streaming:

class base
{
public:

  virtual ~base();
  virtual void print( std::ostream& ) const;
};

std::ostream& operator<<( std::ostream& out, const base& b )
{
  b.print( out ); return out;
}

На самом деле это не работает для математических операций, так как вы хотите возвращать значение [const], а не ссылку, т.е. избегать глупостей, таких как a + b = c;.

Например, определено сложение действительных и комплексных чисел, но в результате получается комплексное число, поэтому вы не можете вывести complex из real. По другому - может быть. Но все же вы хотите определить точный интерфейс операций:

const real operator+( const real&, const real& );
const complex operator+( const complex&, const complex& );

Надеюсь, это даст вам достаточно, чтобы переосмыслить свой дизайн :)

person Nikolai Fetissov    schedule 24.01.2010
comment
Можешь лучше объяснить, что ты имеешь в виду? - person George; 24.01.2010
comment
Редактировать: хорошо понял, но мне это не помогает - person George; 24.01.2010
comment
Спасибо, но я не буду использовать его с реальной семантикой +, так что все в порядке! - person George; 24.01.2010

Пара вещей приходит на ум. Во-первых, вы бы вообще хотели сделать оператор + "виртуальным". Тогда производный оператор + со ссылкой на B будет переопределением из-за ковариации, а не сокрытием реализации базового класса, что здесь и происходит.

Тем не менее, я подозреваю (но не могу сказать наверняка, не скомпилировав тестовый проект), что это действительно решит вашу проблему. Это потому, что стандартным ответом для бинарных операторов является использование статических методов, которые принимают два параметра в классе. C++ STL широко использует эту технику, и я не знаю причин пытаться реализовать бинарные операторы как методы экземпляра, виртуальные или нет. Это слишком запутанно, без реальных плюсов.

person David Gladfelter    schedule 24.01.2010
comment
Исправление: я думаю, как сказал Николай, они на самом деле реализованы как бесплатные методы. Для примера взгляните на перегрузки операторов ‹‹ и ›› в STL для классов потоков. - person David Gladfelter; 24.01.2010
comment
2-е исправление: игнорируйте этот 1-й абзац. Я забыл, что ковариация в C++ предназначена только для возвращаемых типов. - person David Gladfelter; 24.01.2010