Оригинальный вопрос гласит:
Кажется, я помню, что где-то читал
что стоимость виртуального звонка в C #
не так высоко, относительно
говоря , как в C ++.
Обратите внимание на акцент. Другими словами, вопрос можно перефразировать как:
Кажется, я помню, что где-то читал
что в C #, виртуальном и не виртуальном
вызовы одинаково медленны, тогда как в C ++
виртуальный вызов медленнее, чем
не виртуальный вызов ...
Таким образом, спрашивающий не утверждает, что C # быстрее C ++ ни при каких обстоятельствах.
Возможно, бесполезная диверсия, но это вызвало мое любопытство в отношении C ++ с / clr: pure, без использования расширений C ++ / CLI. Компилятор создает IL, который преобразуется в нативный код с помощью JIT, хотя это чистый C ++. Итак, у нас есть способ увидеть, что делает стандартная реализация C ++, если работает на той же платформе, что и C #.
Не виртуальным методом:
struct Plain
{
void Bar() { System::Console::WriteLine("hi"); }
};
Этот код:
Plain *p = new Plain();
p->Bar();
... вызывает код операции call
с определенным именем метода, передавая Bar неявный аргумент this
.
call void <Module>::Plain.Bar(valuetype Plain*)
Сравните с иерархией наследования:
struct Base
{
virtual void Bar() = 0;
};
struct Derived : Base
{
void Bar() { System::Console::WriteLine("hi"); }
};
Теперь, если мы сделаем:
Base *b = new Derived();
b->Bar();
Вместо этого выдается код операции calli
, который переходит на вычисленный адрес - так что перед вызовом много IL. Вернув его обратно в C #, мы увидим, что происходит:
**(*((int*) b))(b);
Другими словами, приведите адрес b
к указателю на int (размер которого совпадает с размером указателя) и возьмите значение в этом месте, которое является адресом виртуальной таблицы, а затем первый элемент в vtable, который является адресом для перехода, разыменовывает его и вызывает его, передавая неявный аргумент this
.
Мы можем настроить виртуальный пример для использования расширений C ++ / CLI:
ref struct Base
{
virtual void Bar() = 0;
};
ref struct Derived : Base
{
virtual void Bar() override { System::Console::WriteLine("hi"); }
};
Base ^b = gcnew Derived();
b->Bar();
Генерирует код операции callvirt
, точно так же, как в C #:
callvirt instance void Base::Bar()
Таким образом, при компиляции с таргетингом на CLR текущий компилятор Microsoft C ++ не имеет таких возможностей для оптимизации, как в C # при использовании стандартных функций каждого языка; для стандартной иерархии классов C ++ компилятор C ++ генерирует код, который содержит жестко запрограммированную логику для обхода vtable, тогда как для класса ref он оставляет JIT вычислять оптимальную реализацию.