this->Func() и Func(this)
От: Андрей Тарасевич Беларусь  
Дата: 28.04.02 17:45
Оценка: 47 (7)
#Имя: FAQ.cpp.functhis
Здравствуйте VVV, Вы писали:

VVV>Если посмотреть с другой стороны, то любой вызов C++ функции можно описать в терминам C функции, т.е. это будет функция, где первым параметром передаётся указатель на какую-то структуру данных (this). Посему, при вызове такой функции из C этот параметр (this) можно задать каким угодно (и NULL в том числе).


В том то и дело, что тут есть одна существенная тонкость, из-за которой вызов метода объекта в С++ нельзя так запросто описать в терминах C функций.

Предположим, что у нас имеется вот такой класс A

struct A 
{ 
  void foo(int i) { cout << (this != NULL ? "not NULL" : "NULL") << " " << i << endl; }
};


Перепишем метод 'A::foo' в виде "эквивалентной" С функции, как ты предлагаешь (если я тебя правильно понял):

void foo(A* this_, int i) { cout << (this_ != NULL ? "not NULL" : "NULL") << " " << i << endl; }


Пусть от класса A и класса B множественно унаследован класс C:

struct B { int x; };

struct C : B, A {};


Рассмотрим вот такой вызов метода 'foo' через null-указатель типа 'C*':

C* p = NULL;

p->foo(5); // (1)


Как будет выглядеть соответствующий вызов С функции 'foo'? Он будет выглядеть вот так:

foo(p, 5); // (2)


Эквивалентны ли эти вызовы (закроем глаза на неопределенное поведение в вызове (1))? Нет, не эквивалентны. При компиляции, например, MSVC++, эти вызовы распечатают разные результаты на экране. Почему так происходит?

Происходит это потому, что в вызове (2) компилятор вынужден сделать стандартную pointer conversion — конверсию типа первого параметра от типа 'C*' к типу 'A*'. При этом, согласно спецификации языка С++, null-указатель одного типа должен быть переведен в null-указатель другого типа. По этой причине в результате вызова (2) внутрь функции 'foo' будет передан null-указатель и неравенство 'this_ != NULL' не выполнится.

А в вызове (1) ситуация совершенно иная. Для того, чтобы взывать метод класса A через указатель типа 'C*', этот указатель тоже необходимо определенным образом "преобразовать". То, что происходит с указателем в таком случае, не является никакой "официальной" конверсией языка С++. Компилятор обязан просто как-то "сдвинуть" указатель типа 'C*' так, чтобы он стал указывать на экземпляр класса A внутри экземпляра класса C. Как компиялтор будет это делать — детали реализации. Никаких требований о том, чтобы null-указатель одного типа при этом первращался именно в null-указатель другого типа, нет и никогда не было. Эти требования не нужны по той простой причине, что стандарт С++ целиком и полностью запрещает такие вызовы. Ни один уважающий себя компилятор не будет тратить время на проверку равенства указателя NULL. Компилятор просто сдвинет указатель на определенное количесво байт. При этом null-указатель перестанет быть null-указателем. По этой самой причине в результате вызова (1) внутри метода 'A::foo' указатель this не будет null-указателем и неравенство 'this_ != NULL' выполнится.

Вот если бы мы сами заставили компилятор выполнить полноценную конверсию указателя при вызове метода

((A*) p)->foo(5); // (3)


тогда результаты выполнения вызовов (2) и (3) совпали бы. Но пользоваться в программах именно такими вызовами только ради того, чтобы сохранить "правильность" преобразования null-указателей, никто, разумеется, не будет. К тому же это потенциальный удар по производительности.

Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.
Best regards,
Андрей Тарасевич
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.