Рассмотрим следующий код с шаблоном разработки метода шаблона:
class A {
public:
void templateMethod() {
doSomething();
}
private:
virtual void doSomething() {
std::cout << “42\n”;
}
};
class B : public A {
private:
void doSomething() override {
std::cout << “43\n”;
}
};
int main() {
// case 1
A a; // value semantics
a.templateMethod(); // knows at compile time that A::doSomething() must be called
// case 2
B b; // value semantics
b.templateMethod(); // knows at compile time that B::doSomething() must be called
// case 3
A& a_or_b_ref = runtime_condition() ? a : b; // ref semantics
a_or_b_ref.templateMethod(); // does not know which doSomething() at compile time, a virtual call is needed
return 0;
}
Мне интересно, может ли компилятор встроить / удалить виртуализацию функции-члена doSomething () в случаях 1 и 2.
Это возможно, если он создает 3 разных фрагмента двоичного кода для templateMethod (): один без встроенного кода и 2 с встроенным A :: doSomething () или B :: doSomething () (который должен вызываться соответственно в случаях 3, 1 и 2)
Знаете ли вы, требуется ли эта оптимизация стандартом, или же ее реализует какой-либо компилятор?
Я знаю, что могу добиться такого же эффекта с помощью ЭЛТ-паттерна и без виртуального, но цель будет менее ясной.
Стандарт не требует оптимизации в целом (иногда он делает все возможное, чтобы их разрешить); он определяет результат, и компилятор должен выяснить, как лучше всего его достичь.
Во всех трех случаях я бы ожидал templateMethod
быть встроенным. Затем компилятор может выполнять дальнейшую оптимизацию; в первых двух случаях он знает динамический тип this
и поэтому может генерировать не виртуальный вызов для doSomething
, (Я бы тогда ожидал, что он встроит эти звонки.)
Посмотрите на сгенерированный код и убедитесь сами.
Оптимизация — это проблема компилятора не стандартного. Это было бы серьезной ошибкой, если бы оптимизация приводила к неуважению или принципам виртуальных функций.
Итак, в 3-м случае:
// case 3
A& b_ref = b; // ref semantics
b_ref.templateMethod();
фактический объект — это B, а фактическая вызываемая функция должна быть той, которая определена в классе B, какой бы ни была ссылка на используемый указатель.
И мой компилятор отображает правильно 43
— если бы он отображал что-нибудь еще, я бы немедленно изменил компилятор …