Двойная диспетчеризация обычно реализуется по-разному в C ++, при этом базовый класс имеет все разные версии (что делает его обслуживающим кошмаром, но таков язык).Проблема с вашей попыткой двойной диспетчеризации состоит в том, что динамическая диспетчеризация найдет наиболее производный тип B
объекта, для которого вы вызываете метод, но тогда аргумент имеет статический тип A*
.Поскольку A
не имеет перегрузки, которая принимает B*
в качестве аргумента, то вызов other->PreCompose(this)
будет неявно повышен с this
до A*
, и вы останетесь с единственной отправкой по второму аргументу.
Что касается реального вопроса: почему компилятор выдает предупреждения?почему мне нужно добавить директивы using A::Precompose
?
Причиной этого являются правила поиска в C ++.Затем компилятор встречает вызов obj.member()
, он должен найти идентификатор member
, и он будет делать это, начиная со статического типа obj
, если ему не удастся найти member
, в этом контексте он будет перемещенвверх по иерархии и поиск в базах статического типа obj
.
Как только найден первый идентификатор, поиск остановится и попытается сопоставить вызов функции с доступными перегрузками, и если вызовне может быть сопоставлено, это вызовет ошибку.Важным моментом здесь является то, что поиск не будет искать дальше в иерархии, если вызов функции не может быть сопоставлен.Добавляя объявление using base::member
, вы переносите идентификатор member
из базового класса в текущую область.
Пример:
struct base {
void foo( const char * ) {}
void foo( int ) {}
};
struct derived : base {
void foo( std::string const & ) {};
};
int main() {
derived d;
d.foo( "Hi" );
d.foo( 5 );
base &b = d;
b.foo( "you" );
b.foo( 5 );
d.base::foo( "there" );
}
Когда компилятор встречает выражение d.foo( "Hi" );
статический тип объекта - derived
, и поиск проверит все функции-члены в derived
, там находится идентификатор foo
, и поиск не будет продолжен вверх.Аргументом единственной доступной перегрузки является std::string const&
, и компилятор добавит неявное преобразование, поэтому, даже если может быть лучшее потенциальное совпадение (base::foo(const char*)
- лучшее совпадение, чем derived::foo(std::string const&)
для этого вызова), оно будет эффективноcall:
d.derived::foo( std::string("Hi") );
Следующее выражение d.foo( 5 );
обрабатывается аналогично, поиск начинается с derived
и он обнаруживает, что там есть функция-член.Но аргумент 5
не может быть неявно преобразован в std::string const &
, и компилятор выдаст ошибку, даже если в base::foo(int)
найдется идеальное совпадение.Обратите внимание, что это ошибка в вызове, а не ошибка в определении класса.
При обработке третьего выражения b.foo( "you" );
статический тип объекта равен base
(обратите внимание, что фактический объектderived
, но тип ссылки base&
), поэтому поиск не будет искать в derived
, а будет начинаться в base
.Он находит две перегрузки, и одна из них соответствует good , поэтому она вызовет base::foo( const char* )
.То же самое касается b.foo(5)
.
Наконец, при добавлении различных перегрузок в наиболее производный класс скрыть перегрузок в базе он не удаляет ихиз объектов, так что вы можете вызвать нужную перегрузку, полностью квалифицировав вызов (который отключает поиск и имеет дополнительный побочный эффект пропуска динамической отправки, если функции были виртуальными), поэтому d.base::foo( "there" )
не будет выполнять поиск ввсе и просто отправьте вызов base::foo( const char* )
.
Если бы вы добавили объявление using base::foo
в класс derived
, вы бы добавили все перегрузки foo
в base
к доступнымперегрузки в derived
, и вызов d.foo( "Hi" );
рассмотрит перегрузки в base
и обнаружит, что наилучшей перегрузкой является base::foo( const char* );
, поэтому она будет фактически выполняться как d.base::foo( "Hi" );
Во многих случаях разработчики не всегда задумываются о том, как на самом деле работают правила поиска, и может быть удивительно, что вызов d.foo( 5 );
завершается неудачно без объявления using base::foo
или, что еще хуже, вызова d.foo( "Hi" );
отправляется на derived::foo( std::string const & )
, когда оно явно хуже перегрузки, чем base::foo( const char* )
.Это одна из причин, по которой компиляторы предупреждают, когда вы скрываете функции-члены.Другая веская причина для этого предупреждения заключается в том, что во многих случаях, когда вы действительно намеревались переопределить виртуальную функцию, вы можете ошибочно изменить сигнатуру:
struct base {
virtual std::string name() const {
return "base";
};
};
struct derived : base {
virtual std::string name() { // missing const!!!!
return "derived";
}
}
int main() {
derived d;
base & b = d;
std::cout << b.name() << std::endl; // "base" ????
}
Небольшая ошибка при попытке переопределить 1088 * функция-член name
(забывая квалификатор const
) означает, что вы фактически создаете другую сигнатуру функции.derived::name
является не переопределением base::name
и, следовательно, вызов name
через ссылку на base
не будет отправлен на derived::name
!!!