Search code examples
c++pointersdatamember

How do pointers to members in C++ work internally?


I am trying to know how pointers to members work in C++.

I looked at some places, and found that they store some sort of offset. And use the fact that members occur in memory in the same order in which they are declared in the class/structure definition.But...

#include <iostream>
#include<typeinfo>
using namespace std;
struct S
{
    S(int n): mi(n) {}
    int mi;
    int k;
    int f()  {return mi+k;}
};

int main()
{
    S s(7);
    int S::*pmi = &S::mi;
    int S::*lop = &S::k;
    int (S::*sf)() = &S::f;
    cout<<&S::mi<<endl<<&S::k<<endl<<&S::f<<endl<<pmi<<endl<<lop<<endl<<sf<<endl;
    cout<<typeid(lop).name()<<endl;
}

I expected to see some sort of offsets. But, all values in the first line with cout<< , gave 1 as output.

I don't understand , if all are giving 1, where is the information regarding offsets being stored?

It'd be really helpful if you can explain what is really going on.


Solution

  • Iostreams insertion operator does not have an overload for pointers to members.

    There is however an overload for bool, and pointers to members are implicitly convertible to bool - a null pointer to member is converted to false, otherwise true. bool is streamed as 1 if true and 0 if false. So the output that you're observing is the fact that none of your pointers to members are null.

    I tried incrementing by ++.. but that too doesn't work..

    Incrementing doesn't work because pointers to members don't support pointer arithmetic.

    So , is there any way of actually seeing the offset values?

    Anything can be observed as a byte array, so you can marvel at the contents of the member pointers with a little helper function:

    template<class T>
    void print_raw(const T& obj) {
        const unsigned char* cptr = reinterpret_cast<const unsigned char*>(&obj);
        const unsigned char* end = cptr + sizeof(T);
        while(cptr < end) {
            printf("%02hhx ", *cptr++); // is there simple way with iostreams?
        }
        std::cout << '\n';
    }
    
    std::cout << "pmi: ";
    print_raw(pmi);
    std::cout << "lop: ";
    print_raw(lop);
    std::cout << "sf:  ";
    print_raw(sf);
    

    Example output on my system:

    pmi: 00 00 00 00 00 00 00 00 
    lop: 04 00 00 00 00 00 00 00 
    sf:  f0 08 40 00 00 00 00 00 00 00 00 00 00 00 00 00 
    

    This may give you some insight about how the compiler has implemented them. Note that if the implementation includes any padding bytes / bits, those could have any value and thus any non-zero value may be meaningless.

    The member object pointer output seems quite obvious. It looks like a little endian 64 bit offset into the object. mi is at offset 0 and k is offset 4 bytes.