如何在C++类的外部调用类的私有方法

 更新时间:2022年09月09日 10:45:15   作者:ithiker  
这篇文章主要介绍了如何在C++类的外部调用类的私有方法,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下

前言

可否在C++类的外部调用类的私有方法呢?既然谈到了这个问题,当然是可以的。

问题

目标是在一个外部function中调用Widget::forbidden()这个private function。限制条件是不能修改Widget类。

class Widget {
   private:
    void forbidden();
  };
void hijack(Widget& w) {
    w.forbidden();  // ERROR!
  }

下面我们一步步来实现这个小目标:

技术准备

显然,我们不可能直接像w.forbidden()类似那样调用,我们必须通过PMF(pointer to member function)来间接调用,所以,下面先简要介绍PMF:

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类,大多数的类也不提供返回私有成员的方法。

既然我们不能通过添加函数返回PMF的方法来进行实践,那么,是否还有其它方法可以在类的外部访问类的私有成员呢?C++提供了显式模板特化的方法。

2. The explicit template instantiation

C++ 标准中提到:

17.7.2 (item 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.]

显式模板特化时,名字访问规则此时不起作用,Stack Overflow 给出了一个例子:

class Foo
{
private:
  struct Bar;

  template<typename T> class Baz { };

public:
  void f();  // does things with Baz<Bar>
};

// explicit instantiation declaration
extern template class Foo::Baz<Foo::Bar>;

这里在显式模板特化Foo::Baz时,可以访问Foo::Baz和Foo::bar

到这里,既然成员访问规则不适用于模板特化,模板特化时可以访问类的私有成员,那么,我们可否将PMF作为模板参数呢,下面将介绍这点。

3. Passing a member-function pointer as a non-type template parameter

在C++中模板参数通常分为两类:

  • 类型参数,一般的参数都是类型参数,用来进行类型替换;
  • 非类型参数,常见的是整型或指针类型的非类型参数

比如下面,其中T是类型参数,size是非类型参数:

template <class T, int size> // size is the non-type parameter
class StaticArray
{
private:
    // The non-type parameter controls the size of the array
    T m_array[size];
 
public:
    T* getArray();
	
    T& operator[](int index)
    {
        return m_array[index];
    }
};

下面我们再看一个指针作为非类型参数的例子:

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只能用SpaceShip::dock的PMF来实例化,这个模板显得不那么通用,所以我们可以将函数指针也作为一个模板参数:

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. Solution. 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<decltype(&Widget::forbidden), &Widget::forbidden>::apply(w)不是显式模板特化,它只是常见的隐式模板实例化。

那么,如何采用显示模板特化的方法你?这就需要使用friend技巧:

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++类的外部调用类的私有方法的文章就介绍到这了,更多相关C++类外部调用类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 链接库动态链接库详细介绍

    链接库动态链接库详细介绍

    静态链接库.lib和动态链接库.dll。其中动态链接库在被使用的时候,通常还提供一个.lib,称为引入库,它主要提供被Dll导出的函数和符号名称,使得链接的时候能够找到dll中对应的函数映射
    2012-11-11
  • C++基本用法实践之移动语义详解

    C++基本用法实践之移动语义详解

    移动(move)语义是C++引入了一种新的内存优化,以避免不必要的拷贝,下面小编就来和大家简单聊聊C++中移动语义的相关使用吧,希望对大家有所帮助
    2023-07-07
  • c++实现哈希桶的步骤

    c++实现哈希桶的步骤

    本文主要介绍了c++实现哈希桶的步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • 详解C语言中write函数

    详解C语言中write函数

    write函数,是一个C语言函数,功能为将数据写入已打开的文件内,这篇文章主要介绍了C语言中write函数,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08
  • C/C++产生指定范围和不定范围随机数的实例代码

    C/C++产生指定范围和不定范围随机数的实例代码

    C/C++产生随机数用到两个函数rand() 和 srand(),这里介绍不指定范围产生随机数和指定范围产生随机数的方法代码大家参考使用
    2013-11-11
  • 利用C++实现简易的.ini配置文件解析器

    利用C++实现简易的.ini配置文件解析器

    这篇文章主要为大家详细介绍了如何基于C++编写一个简易的.ini配置文件解析器,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以了解一下
    2023-03-03
  • Cocos2d-x UI开发之场景切换代码实例

    Cocos2d-x UI开发之场景切换代码实例

    这篇文章主要介绍了Cocos2d-x UI开发之场景切换代码实例,cocos2d-x中的场景切换是通过导演类调用相应的方法完成的,本文通过代码和详细注释来说明,需要的朋友可以参考下
    2014-09-09
  • 实例讲解C语言编程中的结构体对齐

    实例讲解C语言编程中的结构体对齐

    这篇文章主要介绍了C语言编程中的结构体对齐,值得注意的是一些结构体对齐的例子在不同编译器下结果可能会不同,需要的朋友可以参考下
    2016-04-04
  • OpenCV画任意圆弧曲线

    OpenCV画任意圆弧曲线

    这篇文章主要为大家详细介绍了OpenCV画任意圆弧曲线,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • 浅谈C语言中结构体的初始化

    浅谈C语言中结构体的初始化

    本篇文章是对C语言中结构体的初始化进行了详细的分析介绍,需要的朋友参考下
    2013-05-05

最新评论