3.6Kпросмотров
38.4%от подписчиков
2 марта 2026 г.
Score: 3.9K
Доступ к приватным членам. Явная инстанциация
#опытным В прошлый раз мы уже выяснили, что явно инстанцируя шаблонный метод класса, можно написать свою реализацию, которая будет жонглировать непубличными членами в самых виртуозных позах. Но! Мы так и не вышли за пределы класса. Ручная специализация шаблонного метода - это такой же метод класса, поэтому он и умеет трогать приватные поля. Хочется прям снаружи получить доступ к полю и уже не ограничиваться реализацией метода. С++ и это может воплотить в реальность. Хоть стандарт и бьет по рукам за упоминание имен непубличных членов за пределами скоупа класса, все-таки есть исключения из правил: [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<&Foo::data>, его статический член tr будет инициализирован, и в его конструкторе Storage<PtrType>::ptr получит значение. Теперь вы можете получить доступ к нему через: Foo foo;
std::cout << foo.Storage<int Foo::>::ptr; Это работает! То есть мы просто взяли и вывели на консоль значение приватного члена класса, при этом никак не меняя его код. Можно также легко его изменить. И все это четко согласовано со стандартом. Еще один раз шаблоны сломали инкапсуляцию. Да что ж это такое! Спасибо, @SoulslikeEnjoyer, за материалы для поста) Exploit loopholes. Stay cool. #cppcore #template #fun