Reputation: 3873
Good day everyone!
Properties are not implemented in C++. Id est we cannot write
myObject.property = value; // try to set field f_ to value
is property
is private data member. Public data members violate OOP encapsulation rules.
Instead we have to write getter/setter code:
myObject.setField(value); // try to set field f_ to value
This post is about properties emulation in C++. Property call looks like a public data member call but custom UDF code is used to set or get real private data-member value.
What we'd like to do is allow simple interface usage (public data member like, without function call syntax) while underlying getter/setter might be complex (doing more than data assignment/reading only).
The following code (my colleague wrote) presents simple property implementation:
#include <iostream>
template<class C, class M>
inline C* get_parent_this(typename M C::*member_ptr, M*const member_this)
{
//interpret 0 as address of parent object C and find address of its member
char* base = reinterpret_cast<char*>(nullptr);
char* member = reinterpret_cast<char*>( &(reinterpret_cast<typename C*>(base)->*member_ptr) );
//modify member_this with offset = (member - base)
return reinterpret_cast<typename C*>(reinterpret_cast<char*>(member_this) - (member - base) );
}
class Owner
{
int x,pr_y;
int get_y() {return pr_y;}
void set_y(int v) {pr_y = v;}
public:
struct
{
operator int() { return get_parent_this(&Owner::y, this)->get_y(); }
void operator =(int v) { get_parent_this(&Owner::y, this)->set_y(v); }
} y;
};
int main ()
{
Owner ow;
ow.y = 5;
std::cout << ow.y;
if( get_parent_this(&Owner::y, &ow.y) == &ow) std::cout << "OK\n";
if( (char *)&ow.y != (char *)&ow) std::cout << "OK\n";
return 0;
}
There is simple getter/setter pair in the example above that doesn't do any additional job (e.g. integrity check or bounds checking), but this code may be complex performing bounds checking, integrity check, etc. This is test example only.
Don't worry about "strange" code in the get_parent_this
helper template. The most "horrifying" thing there is offset calculation. nullptr (NULL, 0x0) address is used as a stub. We do not write or read from this address. We only use it for owner object offset calculation basing on the subobject address. We can use any address instead of 0x0. So, this doesn't make sense.
Properties usage:
Please, tell us your thoughts!
Thank you!
Upvotes: 1
Views: 1114
Reputation:
Reference objects are already a can of worms. Problems include:
y = max(y, myobject.y)
operator.
, so proxying a class like this is rather troublesomeswap(object1.y, object2.y)
.The way you're using it has additional surprises. For example, if someone wants to invoke the side-effects of your getter, simply writing myobject.y;
won't work: they actually have to invoke the cast to int.
I can't imagine you'll get any advice other than "don't do this" unless you can give a good explanation of why you think you need to emulate properties. (and even then, the advice is likely to be slanted towards explaining why you really don't need it after all)
Upvotes: 2
Reputation: 59811
The code fails to compile for some obvious reasons. Most of the
typenameS
are unnecessary. I suppose nullptr
is some homebrew
#define
(It is not the C++11 nullptr
for sure).
Here is a prettified, compiling version that makes it easier to see what is actually going on:
#include <iostream>
template<class C, class M>
inline C* get_parent_this(M C::*member_ptr, M* const member_this)
{
C* base = NULL;
// !!! this is the tricky bit !!!
char* member = reinterpret_cast<char*>(&(base->*member_ptr));
return reinterpret_cast<C*>(reinterpret_cast<char*>(member_this) - member );
}
class Owner
{
int x, pr_y;
virtual int get_y() {return pr_y;}
void set_y(int v) {pr_y = v;}
public:
struct
{
operator int() { return get_parent_this(&Owner::y, this)->get_y(); }
void operator =(int v) { get_parent_this(&Owner::y, this)->set_y(v); }
} y;
};
The tricky bit: This involves the dereference of a null-pointer. This
is somewhat equal to the way the macro offsetof
in stddef.h
used
to be (and still is) defined in some compilers. This works reliably on
some compilers but is undefined behavior. Here are a few links to
discussions about the topic:
I don't think it is worth repeating the discussion here. I really
don't see what the code is buying you, besides obfuscated public
data. For the few cases where you really need a setter
I would just
write it instead of using this. If your coding style prohibits public
data for idiosyncratic reasons, write a set of macros to define them.
Upvotes: 2