Началась эта маленькая история с того, что один мой молодой коллега, Алексей Заровный, прислал мне на посмотреть вот такую ссылку - http://ideone.com/gyMg9. По этой ссылке размещён очень короткий, но весьма интересный пример на языке C++. Позволю себе привести ниже его копию.
#include <iostream> class Foo { public: void foo() { std::cout << "hello world" << std::endl; } }; int main() { Foo& foo = *(Foo*)NULL; foo.foo(); }
Наверное, не каждый из читателей сразу поймёт, что несмотря на необычный способ вызова метода, код, тем не менее совершенно трезвый и рабочий, хотя, конечно, вряд-ли кто-нибудь напишет это в реальном проекте. Однако для демонстрации понимания основ ООП пример очень достойный.
Признаюсь сразу, что мне понадобилась подсказка. Мой мозг программиста с более чем двадцатилетним стажем, по самое не хочу загруженный различными стереотипами кода, отказался принять представленную конструкцию даже после того, как я убедился, что это компилируется и работает! Однако, мне на радость, есть у меня один добрый такой коллега, который относительно недавно пополнил армию программистов, оставив не менее достойные ряды армии электронщиков. Зовут этого молодого гения Антон Березин :) Именно он подлил в мои заржавленные мозги капельку масла :) И тогда сразу все стало на свои места! Еще бы! Сколько раз я объяснял ЭТО своим ученикам при объяснении принципов реализациии ООП :)
Для тех, кто еще не понял в чем дело я приведу упрощенный вариант тела функции main().
int main() { Foo *foo = (Foo*)0; foo->foo(); }
Есть ли здесь фокус или все совершенно справедливо? Для тех, кто по прежнему не понимает как это может работать приведу пояснения.
Давайте вспомним основы внутренней организации ООП. Если у нас есть некий класс с полями и методами, то что из себя представляет объект этого класса? Для того, чтобы быть понятнее приведу более конкретный пример.
class A { public: void m(); private: int d; } A *a = new A(); a->m();
Итак, что такое a с точки зрения внутренностей языка C++? Наверное, на этот вопрос все ответят правильно. С точки зрения внутреннего устройства, а, это указатель на некую область памяти, где лежат данные класса A. Обратите внимание на очень важную часть ответа - "данные класса".
Конечно же! Экземпляр класса это не более чем распределённые в памяти нестатические данные класса.
Методы класса лежат в памяти отдельно, и в единственном экземпляре для всех объектов класса. Чтобы различать экземпляры данных, каждый метод класса имеет неявный параметр this, через который, в реальности, передается указатель на тот экземпляр данных, к которому относится текущий вызов метода. Таким образом, запись
a->m();
семантически соответствует некой условно-функциональной записи A::m(a), т.е. вызывается метод m() класса A::, которому передается экземпляр данных класса A по указателю a (простите мне синтаксические вольности :) ).
Таким образом, запись ((Foo *)0)->foo(), соответствующая приведённому примеру означает, что был вызван метод foo() класса Foo:: и в него был передан нулевой указатель на данные. Но, так как метод foo() в своей реализации не использует каких-либо данных класса Foo, то и проблем с исполнением этого метода никаких нет.
Вот такой вот интересные пример на понимание организации ООП в языке C++. Тут надо оговориться, что относительно данного контекста, т.е. хранения данных отдельно от методов, организация ООП одинакова для всех языков. Однако, не в каждом языке возможен такой фокус. Где-то мы встретимся с невозможностью синтаксического описания этого фокуса (например, Python), а где-то мы столкнемся с параноидальной моделью безопасности, которая просто не позволит нам сделать какие бы то ни было операции с нулевым объектом класса (например, Java).
В завершении добавлю пару вариаций на тему того, как может выглядеть функция main() в более изощрённом варианте обсуждаемого примера, предложенных моим коллегой по цеху программистов, Игорем Богомоловым.
int main() { Foo& foo = *(Foo*)NULL; void(Foo::*f)(void) = &Foo::foo; (foo.*f)(); }
int main() { Foo *foo = (Foo*)0; void(Foo::*f)(void) = &Foo::foo; (foo->*f)(); }