понедельник, 15 июля 2013 г.

Доступ к protected извне: ловкость рук и никакого мошенничества

Не так чтоб каждый день, но иногда всё же требуется получить доступ к protected методам какого-то класса. Простейший пример — QPlainTextEdit. Сразу несколько методов, непонятно для чего сделанных защищёнными (тот же firstVisibleBlock(), например). Конечно, в теории следует наследоваться от базового класса, и таким образом получить доступ. Но не всегда есть возможность это сделать (если, скажем, базовый класс создаётся в какой-то части системы, которая ничего не знает о классе-наследнике), да и не всегда нужно. На днях у меня как раз возникла такая ситуация, поэтому спешу поделиться решением (найденным в интернете, честно говоря, но несколько доработанным).

Итак, простейший случай: метод не перегружен. Тут всё более-менее просто. Смотрим пример:
 class A  
 {  
 protected:  
   void f() {}  
 };  
   
 class B  
 {  
 public:  
   void g()  
   {  
     A a;  
     //делаем что-то с экземпляром A  
     //а вот теперь небольшой фокус  
     struct AHack : A  
     {  
       using A::f;  
       static void fHack(A *a)  
       {  
         if (!a)  
           return;  
         (a->*&AHack::f)();  
       }  
     };  
     //теперь вызываем защищённый метод  
     AHack::fHack(&a);  
     //продолжаем работу  
   }  
 };  
Вот такой трюк с приведением типов. Хоть мы и наследовались от базового класса, но при вызове метода не создавали новых экземпляров.

Теперь представим, что метод A::f() перегружен:
 class A  
 {  
 protected:  
   void f() {}  
   void f(int) {}  
 };  
Тут компилятор выдаст приблизительно такую ошибку:
 ... ошибка: '& A::f' cannot be used as a member pointer, since it is of type '<unresolved overloaded function type>'  
Что делать? Как говорится, "we need to go deeper" (кто не в курсе — погуглите). Слегка изменим нашу структуру AHack:
 struct AHack : A  
 {  
   //using больше не понадобится  
   static void fHack(A *a)  
   {  
     if (!a)  
       return;  
     reinterpret_cast<AHack *>(a)->f();  
   }  
   static void fHack(A *a, int i)  
   {  
     if (!a)  
       return;  
     reinterpret_cast<AHack *>(a)->f(i);  
   }  
 };  
Такой способ является универсальным и будет работать как в случае с перегруженным методом, так и в случае с неперегруженным. Теперь можно вызывать метод A::f() в обоих вариантах:
 AHack::fHack(&a);  
 AHack::fHack(&a, 10);  
К reinterpret_cast обычно прибегать не рекомендуется, так как малейшая ошибка запросто может привести к падению программы, а найти эту ошибку будет довольно сложно. Если бы мы привели тип A * к типу AHack * и использовали бы какой-то метод, отсутствующий в A, то с большой долей вероятности программа бы упала. Но поскольку мы обращаемся только к методам A, то такое приведение типов можно назвать безопасным (но только в данной ситуации). Подробнее о приведении типов можно почитать в одном весьма и весьма интересном блоге, а у меня на сегодня всё.

Комментариев нет:

Отправить комментарий