Похоже, что это первая статья по программированию. И это-то в блоге программиста со стажем. У меня, между прочим, первый компьютер в шесть лет появился, где-то в девяностом году, тогда приставки-то не у всех были. Ай-йа-йай!

И так, приватный доступ. Приватны ли поля и методы класса на самом деле? Очевидный ответ, раз уж я начал с такого вопроса нет. Сегодня я наткнулся на интересную статью о том как можно обойти этот запрет. Суть предлагаемого метода: получить указатель на экземпляр класса и с помощью арифметики указателей добраться до нужного поля. Я и раньше думал, что это возможно сделать похожим способом, но никогда не пробовал. Чего уж таить, я и до сих пор не попробовал этот способ. Он вообще говоря хакерский, зависит от реализации компилятора, и не очень понятно как можно достучаться до методов класса.

В комментариях всё к той же статье (на текущий момент это первый комментарий) я обратил внимание на ссылку на другую статью. Метод описываемый в ней оказался намного интересней. Настолько интересный, что я несколько часов разбирался что же там на самом деле написано. Я редко пишу на C++ и потому знаю и использую не так много разных «фишек» этого языка. Наверно это сыграло не последнюю роль, почему же мне пришлось так долго въезжать в суть происходящего.

Через несколько часов попыток понять и написать тоже самое только своими силами, я пришёл к удивительно простому выводу (который, как оказалось, я уже успел прочитать в другом месте но тогда я его не смог понять). Доступ к любому члену класса можно обеспечить с помощью нехитрого шаблона:

template<typename T, T field>
struct Accessor {
  T operator() () {
    return field;
  }
};

Можно ли было обойтись без шаблонов? Я весь стандарт не читал, но похоже что нет. Если верить IBM-у, то вот почему оно работает:

Access checking rules do not apply to names in explicit instantiations. Template arguments and names in a declaration of an explicit instantiation may be private types or objects.

Ключевые слова тут: явная реализация. Если попробовать провернуть тоже самое, но с неявной реализацией, компилятор будет ругаться. Также он будет ругаться если попробовать «в лоб» получить ссылку на частный метод или свойство.

Шаблон может и не хитрый, но как же его всё таки использовать? Пусть имеется класс Private с полем privVar и методом privFunc1, тогда для этих членов класса надо создать по экземпляру такой структуры:

struct Accessor<int   Private::*,    &Private::privVar>   acc_privVar;
struct Accessor<void (Private::*)(), &Private::privFunc1> acc_privFunc1;

Здесь, первый параметр шаблона это тип указателя: указатель на поле или на метод; а второй параметр это собственно указатель на интересующий член.

Тогда использовать приватные члены класса можно следующим образом:

p.*acc_privVar() = i;
(p.*acc_privFunc1())();

где p — это экземпляр класса Private.

Что же тут происходит на самом деле? В момент создания экземпляра структуры Accessor компилятор не применяет правила доступа к приватным полям и методам, таким образом информация об их расположении в памяти становится известна внутри структуры Accessor. Хитрый вызов (p.*acc_privFunc1())() состоит из следующих шагов:

  1. acc_privFunc1() — выполняется оператор «скобочки» для экземпляра структуры Accessor acc_privFunc1. Этот оператор возвращает указатель на метод объекта void (Private::*)().
  2. p.*acc_privFunc1() — разыменовывается указатель на член класса. Данное выражение необходимо поставить в скобки в случае разыменования указателя на функцию, так как приоритет этой операции ниже чем приоритет оператора ().
  3. (p.*acc_privFunc1())() — выполняется необходимая функция.

Аналогичные шаги происходят и для поля.

Данный метод демонстрирует следующая программа:

#include <iostream>

struct Private {
private:
  int privVar;

  void privFunc1() {
    std::cout << __func__ << "(), privVar = " << privVar << std::endl;
  }

  int privFunc2(int param) {
    std::cout << __func__ << "(" << param << ") = ";
    return param * param;
  }
};


template<typename T, T field>
struct Accessor {
  T operator() () {
    return field;
  }
};


struct Accessor<void (Private::*)(),    &Private::privFunc1> acc_privFunc1;
struct Accessor<int  (Private::*)(int), &Private::privFunc2> acc_privFunc2;
struct Accessor<int   Private::*,       &Private::privVar>   acc_privVar;


int main(int argc, char *argv[]) {
  Private p;

  for (int i = 1; i < 4; i++) {
    p.*acc_privVar() = i;
    (p.*acc_privFunc1())();
    std::cout << (p.*acc_privFunc2())(i) << std::endl;
  }

  return 0;
}

Если её запустить, то должен получиться вот такой вот результат:

privFunc1(), privVar = 1
privFunc2(1) = 1
privFunc1(), privVar = 2
privFunc2(2) = 4
privFunc1(), privVar = 3
privFunc2(3) = 9

Итак, приватное не приватно, однако.