Question

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:

  1. if someone uses public data member properties may be helpful in case: 1.1. to trace public data member calls if something will go incorrect; 1.2. to upgrade painlessly legacy code that is based on public data member usage;

  1. What do you think about properties emulation in C++? Is it lively idea? Does this idea have drawbacks (show, please)?
  2. What do you think about owner object address calculation from the suboject address? What techniques and possible pitfalls you know?

Please, tell us your thoughts!

Thank you!

Was it helpful?

Solution

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.

OTHER TIPS

Reference objects are already a can of worms. Problems include:

  • They interfere with type deduction, such as y = max(y, myobject.y)
  • You cannot overload operator., so proxying a class like this is rather troublesome
  • Surprises of the sort swap(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)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top