Friday, December 30, 2011

Access to private members: Safer nastiness.

In one of my previous posts I showed some code that accesses a private member, that now has been posted on the boost mailing list as a possible utility for boost serialization to access private attributes (OH MY, I had no idea such a thing actually could have uses!).

I realized that it would be beneficial to have an alternative implementation because the previous code suffered from the Static Initialization Order Fiasco. The following code does not suffer from that problem anymore:

template<typename Tag, typename Tag::type M>
struct Rob { 
  friend typename Tag::type get(Tag) {
    return M;
  }
};
This defines a friend function that can be called by ADL using the tag type.
// use
struct A {
  A(int a):a(a) { }
private:
  int a;
};

// tag used to access A::a
struct A_f { 
  typedef int A::*type;
  friend type get(A_f);
};

template struct Rob<A_f, &A::a>;

int main() {
  A a(42);
  std::cout << "proof: " << a.*get(A_f()) << std::endl;
}
Note that the explicit declaration of "get" inside of "A_f" is important, because we want "get" to be visible to ADL, and the rob template specialization is not an associated class of "A_f" (we could have declared it globally too, if we wanted, but then it would be visible for anyone). To overcome that, we could introduce some utility class we derive from:
template<typename Tag, typename Member>
struct TagBase {
  typedef Member type;
  friend type get(Tag);
};

The definition of a Tag then becomes very simple
struct A_f : TagBase<A_f, int A::*> { };
Unfortunately with the TagBase, GCC spits out a warning about the "friend type get(Tag)" that it declares a non-template function. The warning can be ignored, but it's annoying :).

Hope you enjoyed!

11 comments:

  1. error C2059: syntax error : '<'

    ReplyDelete
  2. Ok, man, dont worry I fugured out that I had to include the upmost code as well, and the error dissapeared. So far so good.

    ReplyDelete
  3. great idea!

    I try use your idom,but got another problem,how to dedue type of member A::a,so i can use marco to do that job.
    Like this:
    class A
    {
    int a,b,c;
    };
    DEF(A,(a)(b)(c))

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. #include
    using namespace std;
    class a{

    private:
    int a_val;
    public:
    a():a_val(20){}

    };

    class a_f{
    public:
    int b;
    };

    int main()
    {
    a temp;
    a_f *temp2;
    temp2=(a_f *)&temp;
    cout<<"\naceesing private a->a_val = "<<(temp2)->b;
    cout<<"\n\n";
    return 0;
    }

    ReplyDelete
  6. Does it works with member functions?

    Because the code below:
    struct A
    {
    private:
    void fff()
    {
    cout << "fff called\n";
    }
    };

    template
    struct Rob
    {
    friend typename Tag::type get(Tag)
    {
    return M;
    }
    };

    struct A_fun
    {
    typedef void(A::*type)();
    friend type get(A_fun);
    };

    template struct Rob;

    int main()
    {
    A a;
    a.*get(A_fun());
    }


    has compile error:
    error: invalid use of non-static member function

    ReplyDelete
  7. I had created a github project for helping access to privates of objects based on this technique. Thanks!

    https://github.com/zjx20/stealer

    ReplyDelete
  8. Hi!
    I just came across your blog post and found it really fascinating. This might become handy in some special unit testing cases. I still don't fully understand why this solution works. Could you please explain why the line

    template struct Rob <A_a, &Access::m_a >;

    works without the compiler complaining about access to private member while this line

    int Access::*type = &Access::m_a;

    would not work? Shouldn't the compiler in both cases see that we are trying to get a pointer to a private member?

    best regards,
    Matthias

    ReplyDelete
  9. @Matthias:
    I am not sure whether you'll read this or not, but in case someone else has the same question, it has to do with the template being explicitly instantiated (as litb already wrote it in his previous post here: http://bloglitb.blogspot.com/2010/07/access-to-private-members-thats-easy.html).
    From the working draft N3337, in 14.7.2 [temp.explicit]:

    12/ The usual access checking rules do not apply to names used to specify explicit instantiations. [Note: In particular, the template arguments and names used in the function declarator (including parameter types, return types and exception specifications) may be private types or objects which would normally not be accessible and the template may be a member template or member function which would not normally be accessible. —endnote]

    ReplyDelete
  10. I tried to explain the core idea as simple as possible over here:

    http://cpp.kjx.cz/private_backdoor.html

    Thanks for the fun litb!

    Honza

    ReplyDelete
  11. You can use

    #pragma GCC diagnostic ignored "-Wnon-template-friend"

    to suppress the warning.

    ReplyDelete