2

I am implementing my C#-like property class in C++. So I have to provide acceess to the internal field(mother::_i) to the property field(mother::i).

I found few solutions but there were no perfects.

Firstly I made a method to provide owner(mother in this case)'s pointer on runtime by calling method like RProperty<...>::SetOwner(mother&). But it requires additional code to use my property class and costs in runtime.

Secondly I came up with idea that this pointer of RProperty and member pointer of itself can find the owner's pointer. obviously, ownerpointer = this - &mother::i. But providing member pointer to member itself gives me compile time error. I tried a tricky method using 'empty' struct to provide member pointer to property. But it turns out sizeof(struct empty) is not zero. it costs unnecessary extra memory per instances. I stucked in this issue for few days.

anyone has a good idea? :)

Code works but not perfect:

#include "stdafx.h"
struct empty{};

template<typename TCLASS, typename TFIELD>
class RPropertyBase
{
protected:
    RPropertyBase(){ }

    TCLASS& getOwner() const {  };
};
template<typename TCLASS, typename TFIELD, TFIELD TCLASS::*PFIELD, empty TCLASS::*PTHIS>
class RProperty : RPropertyBase<TCLASS, TFIELD>
{
protected:
    TCLASS& getOwner() const { return *(TCLASS*)((unsigned int)this-(unsigned int)&(((TCLASS*)0)->*PTHIS)-sizeof(empty) ); }

public:
    RProperty<TCLASS, TFIELD, PFIELD, PTHIS>& operator=(const TFIELD& A){ getOwner().*PFIELD = A; return *this; }
    operator TFIELD&() const { return getOwner().*PFIELD; }
};

class mother
{
    int _i;

    template<typename C>
    struct __Propertyi : public RProperty<C, int, &C::_i, &C::_empty>
    {
        using RProperty<C, int, &C::_i, &C::_empty>::operator=;
    };
public:
    empty _empty;
    __Propertyi<mother> i;
};

int _tmain(int argc, _TCHAR* argv[])
{
    mother a;
    a.i = 1;
    int bb = (a.i);
    return 0;
}
Laie
  • 540
  • 5
  • 14
  • seems an awfully complicated approach, why not a simple setter/getter? Or dare I say, even a simple `struct`? – Nim May 17 '13 at 07:31
  • 1
    It's for eliminating getter/setter hell and studying templates and C++. The result will not be appeared that useless I think. – Laie May 17 '13 at 07:38
  • 2
    `__Propertyi` is a name reserved for the implementation. Please don't use names starting with underscores, or with double underscores. – R. Martinho Fernandes May 17 '13 at 07:48
  • And when you have properties (`_j`, `_k` etc.) you will define the internal class `n` times? or have a template? If you have a template, means you apply a generic bit of logic to all properties; the latter gives you nothing over a `struct` or `class` with public members. If you have a class per type, you've now got `n` classes and then scale this out. My point; it's worse than getter/setter hell. In this case, you are better off using a simple macro, `#define PROPERTY(name, type) type get##name() const {...}; void set##name(type v_) { name = v_; }` if you really wanted to do that... – Nim May 17 '13 at 07:50
  • Fernandes, thanks for that. – Laie May 17 '13 at 08:27
  • @Nim, you suggested macros. This example is simplified version of my code to make others understand easily. That full version also has macros allows users to use property class by one-line just like yours. Now in terms of implementation, both drew. In terms of usage of class, I don't have to use getter/setter. So, I think my project is reasonable. – Laie May 17 '13 at 08:32

1 Answers1

4

First...

So I have to provide acceess to the internal field(mother::_i) to the property field(mother::i).

Identifiers beginning with an underscore are reserved in C++ - only the compiler and it's libraries are supposed to use them. Identifiers containing double-underscores are also reserved. However, identifiers with a single trailing underscore such as i_ are OK.

Getting to the point...

ownerpointer = this - &mother::i

It looks like you're trying to subtract a member pointer from a pointer, which you can't do. Member pointers are a bit like offsets into the layout of a type, but this breaks down in two ways...

  1. It's not the abstraction they're designed to provide.

  2. It's not accurate anyway - once you allow for multiple inheritance and virtual inheritance, the offset at which a particular member appears within a type doesn't just depend on it's position within the base type in which it's defined, but also on which subtype you're looking at.

If you really want to do pointer arithmetic that's aware of the layout of a type, it's certainly possible, but it's a C programming technique that uses C-level features. There's also some significant limitations on context.

The key idea is that instead of trying to use member pointers as offsets, you use actual offsets. This costs you type-safety but, so long as you wrap the type-unsafe code and make absolutely certain it's correct, you should be OK.

The basic tool is offsetof, which is a macro that C++ inherits from C...

offsetof(structname, membername)

You can look up the implementation of that macro, but don't copy it - the standard requires that a compiler provide some way to implement the macro that works, but the implementation that works for one compiler may not work for another. However, two common approaches are...

  • Look at the address of the member in an imaginary instance of the struct at address zero. Problems with this (e.g. that imaginary instance obviously doesn't have a valid virtual pointer) are part of the reason for some restrictions.
  • Use a special "intrinsic" function provided by the compiler, which is one of the reasons why those identifiers with underscores are reserved.

Using that offset, in principle, you can cast your pointer to char* via void*, do your arithmetic, then cast back again to the needed type.

The first problem is obvious - some members (ie the static ones) aren't at a fixed offset in each instance, they're at a fixed address irrespective of the instance. Obvious but perhaps best to say it.

The next problem is from that offsetof documentation I linked...

type shall be a POD class (including unions).

You're looking at the layout of a type. You need that layout to apply to subtypes as well. Because you've discarded the C++ polymorphism abstraction and you're dealing directly with offsets, the compiler can't handle any run-time layout resolution for you. Various inheritance-related issues would invalidate the offset calculations - multiple inheritance, virtual inheritance, a subtype that has a virtual pointer when the base doesn't.

So you need to do your layout with a POD struct. You can get away with single inheritance, but you can't have virtual methods. But there's another annoyance - POD is a bit of an overloaded term that obviously doesn't just relate to whether offsetof is valid or not. A type that has non-POD data members isn't POD.

I hit this problem with a multiway tree data structure. I used offsetof to implement the data structure (because different times). I wrapped this in a template, which used a struct and offsetof to determine the node layouts. In a whole series of compilers and compiler versions this was fine until I switched to a version of GCC, which started warning all over the place.

My question and answer about this on SO are here.

This issue with offsetof may have been addressed in C++11 - I'm not sure. In any case, even though a member within a struct is non-POD, that struct will still have a fixed layout determined at compile time. The offset is OK even if the compiler throws warnings at you, which luckily in GCC can be turned off.

The next problem from that offsetof documentation I linked...

type shall be a standard-layout class (including unions).

This is a new one from C++11 and, to be honest, I haven't really thought about it much myself.

The final problem - actually, the view of a pointer as an address is invalid. Sure, the compiler implements pointers as addresses, but there's lots of technicalities, and compiler writers have been exploiting these in their optimisers.

One area you have to be very careful with once you start doing pointer arithmetic is the compilers "alias analysis" - how it decides whether two pointers might point to the same thing (in order to decide when it can safely keep values in registers and not refer back to memory to see if a write through an alias pointer changed it). I once asked this question about that, but it turns out the answer I accepted is a problem (I should probably go back and do something about it) because although it describes the problem correctly, the solution it suggests (using union-based puns) is only correct for GCC and not guaranteed by the C++ standard.

In the end, my solution was to hide the pointer arithmetic (and char* pointers) in a set of functions...

inline void* Ptr_Add  (void* p1, std::ptrdiff_t p2)
{
  return (((char*) p1) + p2);
}

inline void* Ptr_Sub  (void* p1, std::ptrdiff_t p2)
{
  return (((char*) p1) - p2);
}

inline std::ptrdiff_t Ptr_Diff (void* p1, void* p2)
{
  return (((char*) p1) - ((char*) p2));
}

inline bool  Ptr_EQ (void* p1, void* p2)  {  return (((char*) p1) == ((char*) p2));  }
inline bool  Ptr_NE (void* p1, void* p2)  {  return (((char*) p1) != ((char*) p2));  }
inline bool  Ptr_GT (void* p1, void* p2)  {  return (((char*) p1) >  ((char*) p2));  }
inline bool  Ptr_GE (void* p1, void* p2)  {  return (((char*) p1) >= ((char*) p2));  }
inline bool  Ptr_LT (void* p1, void* p2)  {  return (((char*) p1) <  ((char*) p2));  }
inline bool  Ptr_LE (void* p1, void* p2)  {  return (((char*) p1) <= ((char*) p2));  }

That std::ptrdiff_t type is significant too - the bit-width of a pointer isn't guaranteed to match the bit-width of a long.

Outside of these functions, all pointers are either their correct type or void*. C++ treats void* specially (the compiler knows it can alias other pointer types) so it seems to work, though there may be details I'm not remembering. Sorry - these things are hard, especially these days with optimisers that are sometimes clever in the "obnoxious pedant" sense, and I only touch this evil code if I absolutely have to.

One last issue - I already mentioned that pointers aren't addresses. One oddity is that on some platforms, two different pointers may map to the same address in different address spaces - see for example the Harvard Architecture which has different address spaces for instructions. So even the offset between two pointers is invalid except within certain limits, no doubt described in complicated detail in the standard. A single struct is a single struct - obviously it lives on one address space, with the possible exception of static members - but don't just assume pointer arithmetic is always valid.


Long story short - yes, it's possible to subtract the offset of a member from the address of a member to find the address of the struct, but you have to use actual offsets (not member pointers) and there are limitations and technicalities that may mean you can't even solve your problem this way (e.g. I'm not sure you'll be able to use offsets as template parameters), and certainly mean it's harder than it seems.

Ultimately, the take-away advice is that if you read this, treat it as a warning. Don't do the things I've done. I wish I hadn't, and probably so will you.

Community
  • 1
  • 1
  • I'm so appreciated to see this great answer. Well, it was fairly harder than I thought. I would rather to consider another way to achive it. – Laie May 18 '13 at 07:02