Ответ
#опытным
2 вещи нужно знать(помимо всего остального С++😆), чтобы правильно ответить на квиз выше:
1️⃣ Сокрытие имен
Когда в классе объявляют метод с неким именем, все методы с этим именем из базовых классов становятся невидимыми (скрытыми), независимо от их параметров.
То есть
struct A {
void func(const std::string&);
};
struct B : A {
void func(float);
};
B b;
у объекта b можно вызвать только флотовый вариант func.
Почему так?
Компилятор увидел вызов метода и должен выполнить overload resolution. Он видит, что у объекта b статический тип B и идет смотреть, какие методы из этого класса подходят для вызова.
И вот здесь срабатывает ключевая особенность поиска. Компилятор ищет имя func по цепочке областей видимости — снизу вверх (от производного класса к базовому). Как только он находит хотя бы одно объявление с именем func, поиск по иерархии немедленно останавливается . Дальше вверх (в A) он уже не заглядывает.
В классе B есть func(float) — имя найдено. Поиск завершён. В набор кандидатов для overload resolution попадает только B::func(float). Метод A::func(const std::string&) в этот набор даже не рассматривается — он остался за границей поиска.
Поэтому:
b.func(15.f); // OK: float
b.func("hello"); // Compile Error
2️⃣ Вообще говоря, это сокрытие имен - это проблема с точки зрения привычного нам ООП.
"У меня есть базовый класс, от которого я наследую функциональность. Почему я не могу использовать метод базового класса?"
Сокрытие имен - процесс неявный. Но мы можем явно сказать, какой сокрытый метод мы как разработчики хотим видеть в наследнике.
С помощью директивы using можно вернуть видимость методу базового класса в наследник:
struct A {
void func(const std::string&);
};
struct B : A {
using A::func;
void func(float);
};
B b;
b.func(15.f); // OK
b.func("hello"); // OK
using A::func; позволяет компилятору увидеть метод, принимающий строку, и успешно вызывать его при передаче строкового литерала.
Такой же прием кстати используется в реализации overloaded паттерна.
Когда мы это знаем, правильные ответы находятся на поверхности:
struct A {
void func(const std::string&); // #1
};
struct B : A {
void func(float); // #2
};
struct C : B {
using A::func;
void func(int); // #3
};
C c;
B& b = c;
c.func(3.14f); // вызывает #3 за счет неявного приведения float к int
c.func(123); // вызывает #3
c.func("hello"); // вызывает #1 за счет using
b.func(3.14f); // вызывает #2
Don't let others shadow you. Stay cool
#cppcore
#опытным
2 вещи нужно знать(помимо всего остального С++😆), чтобы правильно ответить на квиз выше:
1️⃣ Сокрытие имен
Когда в классе объявляют метод с неким именем, все методы с этим именем из базовых классов становятся невидимыми (скрытыми), независимо от их параметров.
То есть
struct A {
void func(const std::string&);
};
struct B : A {
void func(float);
};
B b;
у объекта b можно вызвать только флотовый вариант func.
Почему так?
Компилятор увидел вызов метода и должен выполнить overload resolution. Он видит, что у объекта b статический тип B и идет смотреть, какие методы из этого класса подходят для вызова.
И вот здесь срабатывает ключевая особенность поиска. Компилятор ищет имя func по цепочке областей видимости — снизу вверх (от производного класса к базовому). Как только он находит хотя бы одно объявление с именем func, поиск по иерархии немедленно останавливается . Дальше вверх (в A) он уже не заглядывает.
В классе B есть func(float) — имя найдено. Поиск завершён. В набор кандидатов для overload resolution попадает только B::func(float). Метод A::func(const std::string&) в этот набор даже не рассматривается — он остался за границей поиска.
Поэтому:
b.func(15.f); // OK: float
b.func("hello"); // Compile Error
2️⃣ Вообще говоря, это сокрытие имен - это проблема с точки зрения привычного нам ООП.
"У меня есть базовый класс, от которого я наследую функциональность. Почему я не могу использовать метод базового класса?"
Сокрытие имен - процесс неявный. Но мы можем явно сказать, какой сокрытый метод мы как разработчики хотим видеть в наследнике.
С помощью директивы using можно вернуть видимость методу базового класса в наследник:
struct A {
void func(const std::string&);
};
struct B : A {
using A::func;
void func(float);
};
B b;
b.func(15.f); // OK
b.func("hello"); // OK
using A::func; позволяет компилятору увидеть метод, принимающий строку, и успешно вызывать его при передаче строкового литерала.
Такой же прием кстати используется в реализации overloaded паттерна.
Когда мы это знаем, правильные ответы находятся на поверхности:
struct A {
void func(const std::string&); // #1
};
struct B : A {
void func(float); // #2
};
struct C : B {
using A::func;
void func(int); // #3
};
C c;
B& b = c;
c.func(3.14f); // вызывает #3 за счет неявного приведения float к int
c.func(123); // вызывает #3
c.func("hello"); // вызывает #1 за счет using
b.func(3.14f); // вызывает #2
Don't let others shadow you. Stay cool
#cppcore