#c++ #language_lawyer
Есть учебное задание, в котором необходимо получить доступ к private-полям класса извне. struct Cls дана изначально. Я нагуглил что нужно создать копию структуры, но с public методами, а потом через нее стучаться в изначальную. Вопрос в том как это реализовать? Как можно вернуть ссылку на private поля? struct Cls { Cls(char c, double d, int i); private: char c; double d; int i; }; struct B { B(char c, double d, int i); public: char c1; double d1; int i1; }; // Эта функция должна предоставить доступ к полю c объекта cls. // Обратите внимание, что возвращается ссылка на char, т. е. // доступ предоставляется на чтение и запись. char &get_c(Cls &cls) { return ((B*)(&cls))->c1 = 'p'; } // Эта функция должна предоставить доступ к полю d объекта cls. // Обратите внимание, что возвращается ссылка на double, т. е. // доступ предоставляется на чтение и запись. double &get_d(Cls &cls) { /* ... */ } // Эта функция должна предоставить доступ к полю i объекта cls. // Обратите внимание, что возвращается ссылка на int, т. е. // доступ предоставляется на чтение и запись. int &get_i(Cls &cls) { /* ... */ } int main() { Cls cls('h', 2.0, 3); char ch = get_c(&cls); cout << ch << endl; }
Ответы
Ответ 1
Вы не можете честным и надёжным путём получить доступ к приватным данным. Существуют грубые хаки, наподобие «угадать бинарный лэйаут данных и скастить указатель», которые прямо запрещены стандартом, и дают право компилятору наказать вас в любой момент. Правильный ответ на вопрос, как получить доступ к приватным данным — никак. Ваш преподаватель либо задаёт вопрос с подвохом, либо плохо знает язык, который преподаёт. Конкретно в вашем случае, код (B*)(&cls)->c1 нарушает strict aliasing rule (правило 3.10/10 стандарта). Дополнительное чтение по теме: What is the strict aliasing rule? GotW #76: Uses and Abuses of Access Rights. Окей, исходя из развернувшейся дискуссии в ответе @Vlad from Moscow, вопрос о доступе через указатель на «чужой» тип не так уж очевиден даже из стандарта. Как видите, мы покамест не пришли к общему мнению о том, правомерен ли такой доступ по стандарту. В любом случае, доступ к приватным полям — очень плохой стиль программирования, и даже если так возможно сделать, делать этого не нужно. Обновление: Другие ответы: ([1], [2] и [3]) убедили меня в том, что к приватным полям таки можно получить доступ «законным» — то есть, совместимым со стандартом путём. (Впрочем, с трактовкой стандарта в последнем из них я не вполне согласен, но это лишь показывает, что сам по себе стандарт — достаточно большой и не очень ясно написанный текст.) Тем не менее, любой из приведённых подходов кажется мне грубым хаком, и я бы крайне не рекомендовал пользоваться ими в production-коде. Думаю, будет поучительно, если вы отправите ссылку на это обсуждение вашему преподавателю.Ответ 2
Формально можно обмануть компилятор следующим образом #includestruct Cls { Cls(char c, double d, int i) : c( c ), d( d ), i( i ) {} private: char c; double d; int i; }; struct B { B(char c, double d, int i) : c1( c ), d1( d ), i1( i ) {} public: char c1; double d1; int i1; }; char &get_c( Cls &cls ) { void *p = &cls; B *pb = static_cast( p ); return pb->c1 = 'A';; } int main() { Cls cls('h', 2.0, 3); char ch = get_c( cls) ; std::cout << ch << std::endl; } Вывод программы на консоль: A Это совершенно корректный код, так как обе структуры являются layout-compatible и согласно стандарту C++ (3.9.2 Compound types) ...Pointers to cv-qualified and cv-unqualified versions (3.9.3) of layout-compatible types shall have the same value representation and alignment requirements (3.11). Более подробно: 9.2 Class members: 16 Two standard-layout struct (Clause 9) types are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in declaration order) have layout-compatible types (3.9). и 9 Classes: 7 A standard-layout class is a class that: — has no non-static data members of type non-standard-layout class (or array of such types) or reference, — has no virtual functions (10.3) and no virtual base classes (10.1), — has the same access control (Clause 11) for all non-static data members, — has no non-standard-layout base classes, — either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and — has no base classes of the same type as the first non-static data member. 8 A standard-layout struct is a standard-layout class defined with the class-key struct or the class-key class. A standard-layout union is a standard-layout class defined with the class-key union Вы также в main могли бы объявить ссылку на объект типа char. Вот более наглядная программа благодаря добавлению дружественного оператора вывода #include struct Cls { Cls(char c, double d, int i) : c( c ), d( d ), i( i ) {} private: char c; double d; int i; friend std::ostream & operator << ( std::ostream &os, const Cls &cls ) { return os << "c = " << cls.c << ", d = " << cls.d << ", i = " << cls.i; } }; struct B { B(char c, double d, int i) : c1( c ), d1( d ), i1( i ) {} public: char c1; double d1; int i1; }; char &get_c( Cls &cls ) { void *p = &cls; B *pb = static_cast( p ); return pb->c1 = 'A';; } int main() { Cls cls('h', 2.0, 3); char &ch = get_c( cls) ; std::cout << ch << std::endl; ch = 'B'; std::cout << cls << std::endl; Ъ Ее вывод на консоль: A c = B, d = 2, i = 3 Ответ 3
Легальный способ - это явная инстанциация шаблона. Согласно [temp.explicit] p12: Правила доступа не применяются к именам использованным при явных инстанциациях. [ Примечание: в частности, шаблонные аргументы и имена использованные при декларации функции (...) могут быть приватными типами или членами, которые обычно не были бы доступны, и шаблон может быть шаблоном функции-члена класса, которая обычно не была бы доступна. — конец примечания ] Это можно использовать следующим способом: Допустим у нас есть класс class Cls { public: Cls() : i(42) {} virtual ~Cls() {} private: int i; }; Тип указателя на член класса i выглядит как: typedef int Cls::* mem_ptr_t; Напишем шаблонный класс, принимающий шаблонный параметр mem_ptr_t и сохраняюший его в глобальную переменную g_mem_ptr: mem_ptr_t g_mem_ptr; templateclass Helper { static bool b; }; template bool Helper ::b = (g_mem_ptr = P, false); // оператор "запятая" ^ Теперь мы можем инстанциировать Helper<&Cls::i>, и в g_mem_ptr окажется указатель на нужный нам член класса: template class Helper<&Cls::i>; int main() { Cls obj; int& i = obj.*g_mem_ptr; std::cout << i << '\n'; } >>> Код полностью <<< "Библиотечная" версия, для любых классов и членов класса выглядит примерно так: http://coliru.stacked-crooked.com/a/4d4f64adc7a36ab9
Ответ 4
http://ideone.com/gTyt2Q #include#include struct cls { public: cls(char c, double d, int i) : c(c), d(d), i(i) {} private: char c; double d; int i; }; struct copy { public: copy(char c, double d, int i); public: char c; double d; int i; }; int main(void) { cls x('1', 2.5, 3); printf ( "%c %f %d\n", *(char*)((void*)&x + offsetof(copy, c)), *(double*)((void*)&x + offsetof(copy, d)), *(int*)((void*)&x + offsetof(copy, i)) ); return 0; } Вроде бы в комментариях пришли к выводу, что этот код не содержит UB, поскольку: Структуры с одинаковым набором и последовательностью полей имеют одинаковое внутреннее представление. Приведение указателя к другому типу не является UB до тех пор, пока не делается его разыменование. Кроме того, в данном коде приведение делается только к void*. Добавление смещения к адресу структуры даёт указатель на её поле. Указатель на поле приводится к корректному типу, поэтому его разыменование не приводит к UB. Ответ 5
А можно и без функций для доступа (и для разнообразия, а то что-то все про union забыли). int main (int ac, char *av[]) { union u { struct Cls hidden; struct B B; } *up; Cls cls('h', 2.0, 3); B b('A', 3.14, 10); up = (union u *)&cls; cout << up->B.c1 << ' ' << up->B.d1 << ' ' << up->B.i1 << '\n'; up->B = b; cout << up->B.c1 << ' ' << up->B.d1 << ' ' << up->B.i1 << '\n'; } Результат avp@avp-ubu1:hashcode$ g++ c.cpp -std=c++11 avp@avp-ubu1:hashcode$ ./a.out h 2 3 A 3.14 10 avp@avp-ubu1:hashcode$ Если же использовать -std=gnu++11, то union можно сделать безымянным, а приведение типа up = (typeof(up))&cls; вот так.Ответ 6
Вам нужно применить паттерн "Паблик Морозов" #define private public #define protected public #include "your class header.h" #undef private #undef protected После такого подключения все закрытые поля класса, объявленного в Yourclass header.h станут пабликами.Ответ 7
class A { #ifdef BOOST_TEST_MODULE public: #endif
Комментариев нет:
Отправить комментарий