在C++类的外部调用类的私有方法
文章目录
引子
可否在C++类的外部调用类的私有方法呢?既然谈到了这个问题,当然是可以的。
问题
目标是在一个外部function中调用Widget::forbidden()这个private function。限制条件是不能修改Widget类。
class Widget {
private:
void forbidden();
};
void hijack(Widget& w) {
w.forbidden(); // ERROR!
}
下面我们一步步来实现这个小目标:
技术准备
1. pointers to member functions
由于下面会广泛的用到pointers to member functions (PMFs),我们先来回顾一下它的用法:
class Calculator {
float current_val = 0.f;
public:
void clear_value() { current_val = 0.f; };
float value() const {
return current_val;
};
void add(float x) { current_val += x; };
void multiply(float x) { current_val *= x; };
};
在C++11中,使用函数指针调用函数,我们可以这么做:
using Operation = void (Calculator::*)(float);
Operation op1 = &Calculator::add;
Operation op2 = &Calculator::multiply;
using Getter = float (Calculator::*)() const;
Getter get = &Calculator::value;
Calculator calc{};
(calc.*op1)(123.0f); // Calls add
(calc.*op2)(10.0f); // Calls multiply
// Prints 1230.0
std::cout << (calc.*get)() << '\n';
函数指针的一个特性是它可以绑定到类的是由成员函数,下面我们将会用到这点,假设Widget类提供了某种机制来获取其私有成员函数的方法,那么,实现我们的目标就可以像下面这样做:
class Widget {
public:
static auto forbidden_fun() {
return &Widget::forbidden;
}
private:
void forbidden();
};
void hijack(Widget& w) {
using ForbiddenFun = void (Widget::*)();
ForbiddenFun const forbidden_fun =
Widget::forbidden_fun();
// Calls a private member function on the Widget
// instance passed in to the function.
(w.*forbidden_fun)();
}
采用这种办法不错,但是别忘了,我们不能修改Widget类,大多数的类也不提供返回私有成员的方法。
那么怎么办呢,C++标准提供了显式模板特化的方法
2. The explicit template instantiation
类中的模板成员函数在显式模板特化时,可以访问类的私有成员变量或函数,GotW76 给出了一个例子:
// File x.h
//
class X
{
public:
X() : private_(1) { /*...*/ }
template<class T>
void f( const T& t ) { /*...*/ }
int Value() { return private_; }
// ...
private:
int private_;
};
namespace
{
struct Y {};
}
template<>
void X::f( const Y& )
{
private_ = 2; // evil laughter here
}
void Test()
{
X x;
cout << x.Value() << endl; // prints 1
x.f( Y() );
cout << x.Value() << endl; // prints 2
}
这里X::f在显式模板特化时,可以访问X的私有成员变量或函数。
3. Passing a member-function pointer as a non-type template parameter
在C++中模板参数通常是类型,对于非类型参数,C++也支持整型或指针类型,如下:
class SpaceShip {
public:
void dock();
// ...
};
// Member function alias that matches the
// signature of SpaceShip::dock()
using SpaceShipFun = void (SpaceShip::*)();
// spaceship_fun is a pointer-to-member-function
// value which is baked-in to the type of the
// SpaceStation template at compile time.
template <SpaceShipFun spaceship_fun>
class SpaceStation {
// ...
};
// Instantiate a SpaceStation and pass in a
// pointer to member function statically as a
// template argument.
SpaceStation<&SpaceShip::dock> space_station{};
上面的别名SpaceShipFun的使用使得SpaceStation这个模板显得不那么通用,所以我们可以将函数指针也作为一个模板参数:
template <
typename SpaceShipFun,
SpaceShipFun spaceship_fun
>
class SpaceStation {
// ...
};
// Now we must also pass the type of the pointer to
// member function when we instantiate the
// SpaceStation template.
SpaceStation<
void (SpaceShip::*)(),
&SpaceShip::dock
> space_station{};
当然,我们也可以更进一步,在实例化模板的时候让编译器自动推导函数指针的类型:
SpaceStation<
decltype(&SpaceShip::dock),
&SpaceShip::dock
> space_station{};
4. Passing a private pointer-to-member-function as a template parameter
我们结合上面的3个技巧,似乎找到了解决方案:
// The first template parameter is the type
// signature of the pointer-to-member-function.
// The second template parameter is the pointer
// itself.
template <
typename ForbiddenFun,
ForbiddenFun forbidden_fun
> struct HijackImpl {
static void apply(Widget& w) {
// Calls a private method of Widget
(w.*forbidden_fun)();
}
};
// Explicit instantiation is allowed to refer to
// `Widget::forbidden` in a scope where it's not
// normally permissible.
template struct HijackImpl<
decltype(&Widget::forbidden),
&Widget::forbidden
>;
void hijack(Widget& w) {
HijackImpl<
decltype(&Widget::forbidden),
&Widget::forbidden
>::apply(w);
}
运行一下,发现其实不可行:
error: 'forbidden' is a private member of 'Widget'
HijackImpl<decltype(&Widget::forbidden),
&Widget::forbidden>::hijack(w);
主要的原因是因为在hijack函数中使用HijackImpl不是显式模板特化,它只是常见的隐式模板实例化,所以我们还要继续想办法。
5. Friend
提起友员函数或者友员类,大家都很熟悉,我们通常这样定义和使用友员函数:
class Gadget {
// Friend declaration gives `frobnicate` access
// to Gadget's private members.
friend void frobnicate();
private:
void internal() {
// ...
}
};
// Definition as a normal free function
void frobnicate() {
Gadget g;
// OK because `frobnicate()` is a friend of
// `Gadget`.
g.internal();
}
但是不会这样:
class Gadget {
// Free function declared as a friend of Gadget
friend void frobnicate() {
Gadget g;
g.internal(); // Still OK
}
private:
void internal();
};
void do_something() {
// NOT OK: Compiler can't find frobnicate()
// during name lookup
frobnicate();
}
因为frobnicate在Gadget内部,do_something()在做名字查找时不会查找到frobnicate,解决办法如下:
class Gadget {
friend void frobnicate(Gadget& gadget) {
gadget.internal();
}
private:
void internal();
};
void do_something(Gadget& gadget) {
// OK: Compiler is now able to find the
// definition of `frobnicate` inside Gadget
// because ADL adds it to the candidate set for
// name lookup.
frobnicate(gadget);
}
当然如果do_something不带参数,我们在外部重新声明友员函数,也是可以的:
class Gadget {
// Definition stays inside the Gadget class
friend void frobnicate() {
Gadget g;
g.internal();
}
private:
void internal();
};
// An additional namespace-scope declaration makes
// the function available for normal name lookup.
void frobnicate();
void do_something() {
// The compiler can now find the function
frobnicate();
}
了解了这些,对下面这样的代码就不会太吃惊了:
#include <iostream>
template <int N>
class SpookyAction {
friend int observe() {
return N;
}
};
int observe();
int main() {
SpookyAction<42>{};
std::cout << observe() << '\n'; // Prints 42
}
Put the magic pieces together
那么结合上面所有的这些技巧,我们就可以得到下面的代码:
namespace {
// This is a *different* type in every translation
// unit because of the anonymous namespace.
struct TranslationUnitTag {};
}
void hijack(Widget& w);
template <
typename Tag,
typename ForbiddenFun,
ForbiddenFun forbidden_fun
> class HijackImpl {
friend void hijack(Widget& w) {
(w.*forbidden_fun)();
}
};
// Every translation unit gets its own unique
// explicit instantiation because of the
// guaranteed-unique tag parameter.
template class HijackImpl<
TranslationUnitTag,
decltype(&Widget::forbidden),
&Widget::forbidden
>;
完整代码可以参见这里:@wandbox
总结一下:
- 我们通过显式模板特化,将Widget::forbidden的函数指针传递给了HijackImpl示例,特化了模板函数hijack
- 通过友员函数声明,让hijack函数可以在他处被调用,从而达成了小目标
结语
写这些,是希望你将上面的代码用于实际产品代码吗?绝对不要这么做! 不要违背C++的封装的原则,一旦打开潘多拉魔盒,那么你的产品代码虽然看起来可运行,但是将不可维护,不可知,因为你破坏了我们和C++语言以及程序员之间的合约。
写这些的目的主要是怀着好奇的目的,提高对C++语言的了解。
Reference:
上一篇: python中私有属性和私有方法,修改私有属性的值
下一篇: 《mysql基础》6-锁2