使用模板技巧访问私有成员

18

以下内容来自Access to private members: Safer nastiness一文,作者为Johannes Schaub - litb

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

// 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;
}

由于get函数没有在A类中定义,因此如何从a对象中调用get函数?

编辑:

我不明白为什么get函数必须将标签作为参数传递,而不能使用a.*get<A_f>()。 => 好的,这是由于ADL机制造成的。


除了gcc编译器,有其他编译器确认可以运行这个程序吗? - hexist
1
@hexist 我刚在Clang (3.1)和Intel C++ (13.0.0)上验证了这个。 - Nikos C.
1
我认为核心问题是为什么编译器在template struct Rob<A_f, &A::a>;这行代码上没有中止,并给出类似于"A::a在此上下文中是私有的"的提示。毕竟,该代码试图获取一个私有成员的指针。很奇怪。 - Nikos C.
1
@hexist 将Apple LLVM 4.1添加到可运行列表中。看到一个模式在这里... - WhozCraig
2
@JamesMcNellis,我认为你的链接有误。请尝试:http://bloglitb.blogspot.com/2011/12/access-to-private-members-safer.html - bdonlan
显示剩余3条评论
3个回答

8

您没有从 a 中调用 get!实际上,get 返回的是指向 A 内部成员的类指针,其类型为 int A::*,因此您需要一个 A 的实例来访问该值。

例如,让我稍微修改一下您的代码:

struct A {
    A(int a):a(a) { }
    int b;
private:
    int a;
};
void test() {
    auto p = &A::b;
    std::cout << a.*p << std::endl;
}

我是否从a内部调用了pa没有p,这正是你的代码中发生的情况,get函数返回&A::a并且你使用a来读取它的值!这就是全部,没有问题,我认为它会在所有编译器中编译通过。
在这里还有另一个问题:为什么C++允许使用A的私有成员声明模板。C++标准规定:

14.7.2p8通常的访问检查规则不适用于用于指定显式实例化的名称。[注意:特别是,在函数声明符中使用的模板参数和名称(包括参数类型、返回类型和异常说明)可以是通常不可访问的私有类型或对象,并且模板可以是通常不可访问的成员模板或成员函数。]

但如果你尝试实例化或甚至typedef指定的模板,那么你会得到一个错误。让我们稍微修改一下你的例子:
struct A {
private:
    int a;
    friend void f();
};

// Explicit instantiation - OK, no access checks
template struct Rob<A_f, &A::a>;

// Try to use the type in some way - get an error.
struct Rob<A_f, &A::a> r;            // error
typedef struct Rob<A_f, &A::a> R;    // error
void g(struct Rob<A_f, &A::a>);      // error

// However, it's Ok inside a friend function.
void f() {
    Rob<A_f, &A::a> r;               // OK
    typedef Rob<A_f, &A::a> R;       // OK
}

然而在所有正常的使用情况下,您不能获取私有成员的地址,他正在使用模板技巧来解决这个问题。 - hexist
A_f内的friend get函数是无用的,不是吗? - Guillaume Paris
@Guillaume07 当然可以将其删除,仍然能够得到相同的结果。 - BigBoss
我不明白为什么get方法应该有标签作为参数。 - Guillaume Paris
我本以为可以写类似于get<A_f>()这样的东西,但是Visual 2012不喜欢 :/ - Guillaume Paris
显示剩余4条评论

1

这是合法的,因为友元函数始终在全局范围内,即使您将它们实现在类内部。换句话说,这样做:

class A
{
    friend void go() {}
};

仅仅是一个快捷方式:

class A
{
    friend void go();
};

void go() {}

2
但他正在从A的外部添加好友。 - hexist
这句话的意思是全局作用域。 - deleted_user
你说得对,这个答案确实回答了OP关于为什么他可以调用get的问题...尽管OP需要问的隐藏问题是为什么他可以使用这个技巧从A的外部解析出&A::a - hexist

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接