📜 ⬆️ ⬇️

How to fit property in one byte?

Introduction


Many programming languages ​​have a tool like properties: C #, Python, Kotlin, Ruby, etc. This tool allows you to call some class method when referring to its "field". In standard C ++, they are not there. If you want to know how you can implement them, I ask for cat.


Some moments...



Methods


Everyone knows the implementation using the get_x and set_x .


 class Complicated { private: int x; public: int get_x() { std::cout << "x getter called" << std::endl; return x; } int set_x(int v) { x = v; std::cout << "x setter called" << std::endl; return x; } }; 

It is the most obvious solution, besides, no “extra” variables are stored in runtime (except for field x , it is called backing field , not necessarily and not superfluous), its main drawback is that expressions that logically mean cx = (cx * cx) - 2 * (cx = cx / (4 + cx)) (specifically there is little sense in this example), turn into c.set_x((c.get_x() * c.get_x()) - 2 * c.set_x(c.get_x() / (4 + c.get_x()))) . And I want the expression in the code to look the same as in my head.


Comment

You can customize the code in any way: add somewhere inline or change the return type to void , remove the backing field or one of the methods, eventually assign const and volatile - this does not affect the reasoning. The set of function calls for such a simple arithmetic expression looks at least ugly .


Operators


In C ++, as in most other languages, you can overload the operators (+, -, *, /,%, ...). But to do this, you need a wrapper object.


 class Complicated { public: class __property { private: int val; public: operator int() { // get std::cout << "x getter called" << std::endl; return val; } int operator=(int v) { // set val = v; std::cout << "x setter called" << std::endl; return val; } } x; }; 

Now cx = (cx * cx) - 2 * (cx = cx / (4 + cx)) looks like a human being. What if we need to have access to other fields Complicated ?


 class Complicated { public: Axis a; class __property { public: operator int() { // get std::cout << "x getter called" << std::endl; return a.get_x(); // ???  'a'  __property  } int operator=(int v) { // set std::cout << "x setter called" << std::endl; return a.set_x(v); // ???  'a'  __property  } } x; }; 

Since the operators are reloaded inside the Complicated::__property , this also has the type Complicated::__property const* . In other words, in the expression cx = 2 object x knows nothing at all about the object c . However, if the implementation of the getter and setter does not require anything from Complicated , this option is quite logical.


Comments
  • Axis is an object that implements, for example, physics on an axis.
  • You can make the __property anonymous class.
  • If the property has no backing field , the x object will occupy one byte, and not 0. Here it is described quite clearly why. Due to alignment, this figure may increase. So if every byte of memory is very important to you, you need to use only the first option: a separate __property class __property needed to overload operators.

Saving this


The previous example requires access to Complicated . The same terminology property implies that get_x and set_x will be defined as methods Complicated . And to call a method inside Complicated , __property needs to know this from there.


This method is also quite obvious but not the best. Just store pointers to whatever you like: the getter method, the setter method, this external class, and so on. I have seen such implementations and do not understand why people consider them acceptable. The size of a property increases to 32 (64) bits, or even more, and the pointer is obtained for a memory that is very close to this for the property (almost indicates itself, below will be explained why). Here is my minimalistic version, it is very appropriate to use the link instead of the pointer.


 class Complicated { private: Axis a; public: int get_x() { std::cout << "x getter called" << std::endl; return a.get_x(); } int set_x(int v) { std::cout << "x setter called" << std::endl; return a.set_x(v); } class __property { private: Complicated& self; public: __property(Complicated& s): self(s) {} inline operator int() { // get return self.get_x(); } inline int operator=(int v) { // set return self.set_x(v); } } x; Complicated(): x { *this } {} }; 

This approach can be called an improved version of the first: it fully contains Methods (UPD: It and the following approaches are completely backward compatible with the project, which used getters and setters as Complicated methods). As you can see, the functionality is defined in Complicated , and __property has acquired a less abstract form. However, I don’t like this implementation because of its price in runtime and the need to enter property initialization into the constructor.


Getting this


The x field should not exist outside the Complicated object, and if the wrapper class is also anonymous, then each x almost guaranteed to be in some Complicated object. This means that it is relatively safe to get this from the outer class by subtracting its indent from the pointer to x relative to the beginning of Complicated .


 class Complicated { private: Axis a; public: int get_x() { // get std::cout << "x getter called" << std::endl; return a.get_x(); } int set_x(int v) { // set std::cout << "x setter called" << std::endl; return a.set_x(v); } class __property { private: inline Complicated* get_this() { return reinterpret_cast<Complicated*>(reinterpret_cast<char*>(this) - offsetof(Complicated, x)); } public: inline operator int() { return get_this()->get_x(); } inline int operator=(int v) { return get_this()->set_x(v); } } x; }; 

Here __property also has an abstract character, therefore it will be possible to generalize it if necessary. The only drawback is the offsetof for complex (non- POD , hence Complicated ) types is not applicable, gcc warns about this (unlike MSVC, which apparently inserts what you need into the offsetof).


Therefore, you have to wrap __property in a simple structure ( PropertyHandler ), to which the offsetof is applicable, and then bring this from PropertyHandler to this from Complicated using static_cast (if Complicated inherits from PropertyHandler ), which correctly calculates all indents.


Final version


 template<class T> struct PropertyHandler { struct Property { private: inline const T* get_this() const { return static_cast<const T*>( reinterpret_cast<const PropertyHandler*>( reinterpret_cast<const char*>(this) - offsetof(PropertyHandler, x) ) ); } inline T* get_this() { return static_cast<T*>( reinterpret_cast<PropertyHandler*>( reinterpret_cast<char*>(this) - offsetof(PropertyHandler, x) ) ); } public: inline int operator=(int v) { return get_this()->set_x(v); } inline operator int() { return get_this()->get_x(); } } x; }; class Complicated: PropertyHandler<Complicated> { private: Axis a; public: int get_x() { std::cout << "x getter called" << std::endl; return a.get_x(); } int set_x(int v) { std::cout << "x setter called" << std::endl; return a.set_x(v); } }; 

As you can see, I already had to create a template so that I could execute static_cast , however, to summarize the definition of Property for very convenient use does not work: it is just completely crutch-like with macros (the name property does not lend itself to customization in Complicated).


This implementation without a backing field takes up only one unused byte (excluding alignment)! And it works the same way as implementation with pointers. With the backing field, it will not take a single “extra” byte, what else is needed for happiness?


The main disadvantage of this approach is the source code curve, but I believe that the syntactic sugar that it brings is worth the effort.


Improvement options
  • The richness of C ++ allows you to redefine other operators in your own way (assignments, binary operations, etc.), therefore, in some cases it makes sense to implement such a property for yourself, because some keyword or two ampersands (do not forget to overload the operators for rvalue if large objects are used) in the right place can significantly improve the speed of the program. New horizons of debugging are also opening ...
  • You can enjoy better access modifiers than in C #! If you think well and put the right keywords in the right places, of course.
  • Property can make some api more pleasant, for example, size() in containers in STL can thus turn into size (specifically in this example it makes sense to take one of the first implementations, and not the last one - the most sophisticated), or the same begin with end 'om ...

UPD: In fact, the price (in one byte) does not depend on the number of property, because you can put them all in the union.


 template<class T> struct PropertyHandler { struct PropertyBase { protected: inline const T* get_this() const { return static_cast<const T*>( reinterpret_cast<const PropertyHandler*>( reinterpret_cast<const char*>(this) - offsetof(PropertyHandler, x) ) ); } inline T* get_this() { return static_cast<T*>( reinterpret_cast<PropertyHandler*>( reinterpret_cast<char*>(this) - offsetof(PropertyHandler, x) ) ); } }; union { class __x: PropertyBase { public: inline int operator=(int v) { return get_this()->set_x(v); } inline operator int() { return get_this()->get_x(); } } x; class __y: PropertyBase { public: inline double operator=(double v) { return get_this()->set_y(v); } inline operator double() { return get_this()->get_y(); } } y; }; }; class Complicated: public PropertyHandler<Complicated> { public: int get_x() { std::cout << "x getter called" << std::endl; return 1; } int set_x(int v) { std::cout << "x setter called" << std::endl; return 2 + v; } double get_y() { std::cout << "y getter called" << std::endl; return 3; } double set_y(double v) { std::cout << "y setter called" << std::endl; return 3 + v; } }; 

')

Source: https://habr.com/ru/post/346936/


All Articles