TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.37K subscribers
Post #1015 4.09K
​​Доступ к приватным членам. Явная инстанциация
#опытным

В прошлый раз мы уже выяснили, что явно инстанцируя шаблонный метод класса, можно написать свою реализацию, которая будет жонглировать непубличными членами в самых виртуозных позах.

Но!

Мы так и не вышли за пределы класса. Ручная специализация шаблонного метода - это такой же метод класса, поэтому он и умеет трогать приватные поля.

Хочется прям снаружи получить доступ к полю и уже не ограничиваться реализацией метода.

С++ и это может воплотить в реальность.

Хоть стандарт и бьет по рукам за упоминание имен непубличных членов за пределами скоупа класса, все-таки есть исключения из правил:

[temp.spec.partial.general]/10

The usual access checking rules do not apply to non-dependent names used to specify template arguments of the simple-template-id of the partial specialization.

[temp.spec.general]/6
The usual access checking rules do not apply to names in a declaration of an explicit instantiation or explicit specialization

Если по-человечески, то проверка доступа к имени не проверяется при явной специализации и инстанциации шаблона. То есть:

class Foo {
private:
int data = 42;
};

template <auto V>
struct Bar {};
template struct Bar<&Foo::data>;


Этот код вполне влиден. Здесь мы используем указатель на поле класса Foo::data в качестве NTTP. Это валидно, потому что указатель на поле класса - это по сути смещение от начала объекта и оно известно на момент компиляции.

Однако, даже если вы явно инстанцируете шаблон, содержащий приватные типы, вы не сможете создать такой объект напрямую.

Bar<&Foo::data> b;
// error: 'int Foo::data' is private within this context


Выход заключается в том, чтобы сохранить значение указателя в статическом члене и передать его в другой класс.

template <typename PtrType>
struct Storage {
inline static PtrType ptr;
};

template <auto V>
struct PtrTaker {
struct Transferer {
Transferer() {
Storage<decltype(V)>::ptr = V;
}
};
inline static Transferer tr;
};

template struct PtrTaker<&Foo::data>;


Когда вы явно инстанцируете PtrTaker&lt;&amp;Foo::data&gt;, его статический член tr будет инициализирован, и в его конструкторе Storage&lt;PtrType&gt;::ptr получит значение. Теперь вы можете получить доступ к нему через:

Foo foo;
std::cout << foo.Storage<int Foo::>::ptr;


Это работает!

То есть мы просто взяли и вывели на консоль значение приватного члена класса, при этом никак не меняя его код. Можно также легко его изменить. И все это четко согласовано со стандартом.

Еще один раз шаблоны сломали инкапсуляцию. Да что ж это такое!

Спасибо, @SoulslikeEnjoyer, за материалы для поста)

Exploit loopholes. Stay cool.

#cppcore #template #fun
  • ❤ 12
  • 👍 8
  • 🔥 6
  • 🤯 6
  • ❤‍🔥 4
More from @grokaemcpp
  1. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  2. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  3. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  4. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  5. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
  6. Sep 25, 2026​​std::spanstream #опытным Радостная весть для всех, кто пользуется iostreams! В C++23 доб…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →