1

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!

nickolay
  • 3,643
  • 3
  • 32
  • 40
  • 4
    I don't really understand. What is the problem with a public data member? – pmr Feb 07 '12 at 22:17
  • 1
    Luckily, in idiomatic C++ you will often find that neither "properties" nor getters/setters are quite as necessary as you may think. – Kerrek SB Feb 07 '12 at 22:31
  • On second reading, this must be the most horrifying code I've read in a while. Not "bad", mind you, just "horrifying", in the sense of causing deep existential disquiet. – Kerrek SB Feb 07 '12 at 22:32
  • 1
    I'm still staring at `&(reinterpret_cast(base)->*member_ptr)` in shock and horror. – pmr Feb 07 '12 at 22:38
  • 2
    @pmr Never done something else in *set*-methods than setting the actual value? – Niklas R Feb 07 '12 at 22:41
  • @pmr, will this work? Isn't he setting `base` to pretty much `NULL` ? –  Feb 07 '12 at 22:42
  • @Muggen Umh, well. See my answer. – pmr Feb 07 '12 at 23:09
  • @pmr Public data member violates OOP encapsulation rules. Property is something looking like a public data member but use custom UDF code to set or get real private data-member value. There is simple getter/setter pair in the example above that doesn't do any additional job (e.g. integrity check or bounds checking). This is test example only. – nickolay Feb 08 '12 at 06:03
  • @NiklasR Yep. You've understand the idea correctly. We need simple interface and ability to do "something else" in setter/getter. – nickolay Feb 08 '12 at 06:05
  • @KerrekSB Don't worry. The most "horrifying" thing is offset calculation. nullptr (NULL, 0x0) address is used as a stub. We do not write or read from there. We only use it for owner object offset calculation basing on the subobject address. We can use any address instead of 0x0... – nickolay Feb 08 '12 at 06:24
  • @DaddyM Which is UB, as my post tries to explain. – pmr Feb 08 '12 at 08:51
  • @pmr Exxuse me, I dunno what "UB" means. Please, clarify – nickolay Feb 08 '12 at 09:03
  • 1
    @DaddyM Undefined behavior. http://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior gives a few examples and explains the meaning of the term quite well. – pmr Feb 08 '12 at 09:17

2 Answers2

2

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.

Community
  • 1
  • 1
pmr
  • 58,701
  • 10
  • 113
  • 156
  • @DaddyM If you say so. I'm not going to argue with you. I've given you a compiling, cleaner version of the mess you posted and pointed out it's flaws. It's still up to you to shoot yourself in the foot with it. – pmr Feb 08 '12 at 08:52
  • calm down. I appreciate your help. – nickolay Feb 08 '12 at 09:02
  • This is useful when the data used to be in a public data member, but then changed to needing a getter and a setter so that some code can do something when it is set. I think it is odd that these property emulations are not used more often -- I find it much more readable then getter and setters. Property emulation is similar to things like the fake references used by `std::vector` – qbt937 Dec 13 '14 at 04:50
  • @qbt937 This is the first time I heard someone mention `vector` as it were a good thing. – pmr Dec 14 '14 at 21:40
  • @pmr It is a good thing. Being able to do that with operator overloading is one reason why c++ is much better than java. Overloading `operator==` and `operator[]` can make code much more readable. – qbt937 Dec 14 '14 at 23:53
  • @qbt937 That's not what I'm doubting. Reference types is what I'm doubting (and so does a large part of the C++ community, including the people that designed `vector`). And I hate to break it to you: you are about two years late to the party. I don't see what contribution to this answer you are trying to make. – pmr Dec 15 '14 at 00:10
  • Sorry I didn't check the time. I meant `operator=` not `operator==`. – qbt937 Dec 15 '14 at 01:10
2

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)