There is a learning task in which you need to access the private fields of the class from the outside. struct Cls given initially. I nagulit that you need to create a copy of the structure, but with public methods, and then through it to knock on the original one. The question is how to implement it? How can I return a link to a private field?

 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; } 
  • This is where are such training tasks? What you do not understand in the above code? - Igor
  • I don’t understand how to get a link to the first class private field, I use the second - Alexander Dvortsov
  • four
    #define private public - zenden2k
  • @ zenden2k: pure UB. - VladD
  • @VladD, if all the code is in one file, then definitely no UB. Update: or is it not related to defiine? - Qwertiy

7 answers 7

You cannot honestly and securely access private data. There are rude hacks, like “guessing binary data and pointer mapping”, which are expressly prohibited by the standard, and give the compiler the right to punish you at any time.

The correct answer to the question of how to get access to private data - no way. Your teacher either asks a trick question or does not know the language that he teaches.

Specifically in your case, the code

 (B*)(&cls)->c1 

violates the strict aliasing rule (rule 3.10 / 10 standard ).

Additional reading on the topic:


Okay, based on the discussion that has developed in the answer to @Vlad from Moscow, the question of access via a pointer to an “alien” type is not that obvious even from the standard. As you can see, for the time being we have not come to a common opinion about whether such access is legitimate by standard. In any case, access to private fields is a very bad programming style, and even if it is possible to do this, it is not necessary to do this.


Update : Other answers: ( [1] , [2] and [3] ) convinced me that private fields can still be accessed by “legal” —that is, in a way compatible with the standard. (However, I do not quite agree with the interpretation of the standard in the last of them, but this only shows that the standard itself is quite a large and not very clearly written text.)

Nevertheless, any of the above approaches seems to me a rude hack, and I would highly not recommend using them in production-code. I think it would be instructive if you send a link to this discussion to your teacher.

  • There is no UB in casting a pointer to another type. It is in mixed references to the same data through pointers of different types. In this case, there is no confusion, since the first call on the pointer of a new type (and its assignment) is done after the last on the pointer of the old one. The rule of the absence of mixed calls is directed to the caching of values ​​- if you change through another type, then there the value can come from the cached copy and turn out to be old. And compilers like clang'a especially carefully work with explicit castes. There is nothing like this in this code, i.e. it is correct and does not contain UB. - Qwertiy
  • @Qwertiy: It seems to me that the standard does not agree with you. As far as I understand the standard, any reference to an object through an incompatible type pointer is UB. - VladD
  • By the way, one more thought. If a double field is in the structure and I access it through a pointer to a double type, then there is no violation either, regardless of how I received this pointer — even though I entered it from the keyboard. - Qwertiy
  • @Qwertiy: Yes, but this is not the problem, but in -> , the arrow calculates the offset. In any case, if you have a link to the desired item in the standard, it will be cool. - VladD pm
  • one
    In fact, there is a way, see my answer) - Abyx

Formally, you can fool the compiler as follows

 #include <iostream> struct 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<B *>( p ); return pb->c1 = 'A';; } int main() { Cls cls('h', 2.0, 3); char ch = get_c( cls) ; std::cout << ch << std::endl; } 

The output of the program to the console:

 A 

This is absolutely correct code, since both structures are layout-compatible and according to the C ++ standard (3.9.2 Compound types)

  1. ... Pointers to cv-unqualified versions (3.9.3).

In more detail: 9.2 Class members:

If you have the same number of non-static data members, there are two types of layout-compatible types (3.9).

and 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,

There are no data or other data types. the first non-static data member.

8 A standard-layout struct-a-class structure with a class-key. A standard layout layout with a class-key union.

You could also declare a reference to an object of type char in main . Here's a more intuitive program by adding friendly output statement

 #include <iostream> 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<B *>( 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; Ъ 

Its output to the console:

 A c = B, d = 2, i = 3 
  • Uh ... pointer aliasing? - VladD 1:51 pm
  • @VladD See my updated answer. - Vlad from Moscow
  • Hm All my life I thought I could not. I reread the standard. - VladD
  • one
    @VladD I just asked the corresponding question on the C ++ standard discussion forum. So let's wait what the experts from all over the world will say in this regard. :) I will report on the results. - Vlad from Moscow
  • one
    @Alexander Dvortsov You also have the following statement in the original example return ((B *) (& cls)) -> c1 = 'p'; I simply replaced 'p' nb 'A' for clarity and returned a reference to this object, which has just been assigned a new value. - Vlad from Moscow

The legal way is to explicitly instantiate a template.
According to [temp.explicit] p12 :

Access rules do not apply to names used in explicit instantiations. [Note: in particular, the template arguments and names used in the declaration of the function (...) may be private types or members that would normally not be available, and the template may be a template for a member function of a class that would not normally be available. - end of note]

This can be used in the following way:
Suppose we have a class

 class Cls { public: Cls() : i(42) {} virtual ~Cls() {} private: int i; }; 

The type of pointer to a member of class i looks like:

 typedef int Cls::* mem_ptr_t; 

Let's write a template class that takes the mem_ptr_t template parameter and saves it to the global variable g_mem_ptr :

 mem_ptr_t g_mem_ptr; template<mem_ptr_t P> class Helper { static bool b; }; template<mem_ptr_t P> bool Helper<P>::b = (g_mem_ptr = P, false); // оператор "запятая" ^ 

Now we can instantiate Helper<&Cls::i> , and in g_mem_ptr will be a pointer to the class member we need:

 template class Helper<&Cls::i>; int main() { Cls obj; int& i = obj.*g_mem_ptr; std::cout << i << '\n'; } 

>>> Fully <<<

The "library" version, for any classes and class members, looks like this:
http://coliru.stacked-crooked.com/a/4d4f64adc7a36ab9

  • Ogogo! C ++ standards are dark and confusing. Horror. And how do you evaluate the ideologically simpler Qwertiy method ? - VladD
  • @VladD Even if there is no violation of the rules strict aliasing, then this only works for standard layout types, i.e. no virtual functions, multiple inheritance, all members in one private / protected / public block. - Abyx
  • I understand this. There seems to be no violation, offsetof fictitious class is used only to get the offsetof , and only double* dereferenced. The question is, do we have the right to dereference the double* that points to the field ? That is, the compatibility of double Class::* with double* . - VladD
  • @VladD, everything should be fine here, the offsetof is needed for this. - Abyx
  • A cool way :) - Qwertiy

http://ideone.com/gTyt2Q

 #include <stdio.h> #include <stddef.h> 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; } 

It seems to be in the comments came to the conclusion that this code does not contain UB, because:

  • Structures with the same set and sequence of fields have the same internal representation.
  • Casting a pointer to another type is not UB until it is dereferenced. In addition, in this code, the cast is done only to void* .
  • Adding an offset to the address of the structure gives a pointer to its field.
  • The pointer to the field is cast to the correct type, so its dereference does not lead to UB.
  • Maybe add a few words about standard compilance? - VladD
  • @VladD wrote. - Qwertiy
  • the same internal representation is only about the standard layout types. - Abyx

And it is possible without access functions (and for a change, but they have forgotten all about the union ).

 int main (int ac, char *av[]) { union u { struct Cls hidden; struct BB; } *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'; } 

Result

 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$ 

If you use -std=gnu++11 , then the union can be made nameless, and the type cast up = (typeof(up))&cls; like this.

  • So anyway, UB. From the union according to the standard, you can read like only what is actually put there. (Tricks for turning double into bytes do not correspond to the standard.) - VladD
  • @VladD, well, in fact it works in practice. And where it doesn't work, you have to either change the compiler, or ... think of something else (as appropriate). Anyway, because absolute portability of programs is something perfect (like communism) - avp
  • Well, it's on gcc. Plus, if UB is really there, then at any time (that is, in any new version of the compiler), the optimizer can throw itself on the breach and take down the whole function. - VladD
  • @VladD, in that case it is necessary to remember the assembler -) - avp

You need to apply the pattern "Public Frost"

 #define private public #define protected public #include "your class header.h" #undef private #undef protected 

After such a connection, all private fields of the class declared in Yourclass header.h will become public.

  • for some reason did not work in Xcode - Sergei Krivonos
 class A { #ifdef BOOST_TEST_MODULE public: #endif