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

本文探讨了如何在C++中通过显式模板特化和友员函数等技术调用类的私有成员函数,详细解释了pointer-to-member-function、模板非类型参数以及友员函数的概念,并给出了实现示例。尽管这种方法可行,但并不推荐在实际产品代码中使用,因为它违反了封装原则。

引子

可否在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{};

到这里,我们似乎找到了解决方案,通过显示模板特化导出本来对外部不可见的函数指针,通过函数指针调用是由函数

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

总结一下:

  1. 我们通过显式模板特化,将Widget::forbidden的函数指针传递给了HijackImpl示例,特化了模板函数hijack
  2. 通过友员函数声明,让hijack函数可以在他处被调用,从而达成了小目标

结语

写这些,是希望你将上面的代码用于实际产品代码吗?绝对不要这么做! 不要违背C++的封装的原则,一旦打开潘多拉魔盒,那么你的产品代码虽然看起来可运行,但是将不可维护,不可知,因为你破坏了我们和C++语言以及程序员之间的合约。

写这些的目的主要是怀着好奇的目的,提高对C++语言的了解。


Reference:

  1. https://accu.org/journals/overload/28/156/harrison_2776/
  2. http://www.gotw.ca/gotw/076.htm
<think>首先,用户的问题是:“我想了解在C++中主函数如何调用外部私有声明”。用户指的是“主函数调用外部私有声明”,这可能是个误解。让我解析一下。 用户可能想问的是:如何在主函数中访问另一个私有成员。因为私有成员不能直接从外部访问,包括主函数(main函数)。用户提到了“外部私有声明”,这可能是指私有成员变量或成员函数。 回顾引用中的关键点: - 引用[1]:私有成员不能直接访问,破坏了信息隐藏。 - 引用[2]:私有部分只能由内函数访问。 - 引用[3]:私有成员仅内可见。 - 引用[4]:关于静态成员的初始化。 - 引用[5]:显示了一个例子,其中通过友元函数访问私有成员。 用户的问题是关于“主函数调用外部私有声明”。我认为“私有声明”可能是个笔误,应该是“私有成员”。在C++中,可以嵌套,私有只能在内部访问。但那可能太具体了。更合理的解释是:用户想问如何在主函数中访问另一个私有成员。 基于引用: - 引用[5]中有一个例子,使用了友元函数(friend function)来访问私有成员。 - 其他引用强调私有成员不能直接访问,必须通过公共接口或友元。 所以,我需要解释如何在主函数中间接访问私有成员: 1. 通过公共成员函数(getter/setter)。 2. 通过友元函数或友元。 3. 如果是静态私有成员,可以通过静态函数访问(如引用[4]所示)。 用户的问题说“外部私有声明”,可能指的是一个中定义的私有嵌套。在C++中,一个可以包含另一个的声明作为私有成员。如果是这样,那么私有嵌套只能在外部的内部访问。 例如: ```cpp class Outer { private: class Inner { // private nested class }; public: // ... }; ``` 在主函数中,不能直接使用Outer::Inner,因为它是私有的。 所以,主函数如何访问这样的私有嵌套? - 不能直接访问,因为它是私有的。 - 必须通过外部的公共成员函数来创建或使用该私有的实例。 - 或者使用友元关系。 在回答中,我需要: - 使用中文。 - 结构清晰。 - 生成相关问题。 - 引用末尾添加引用标识。 - 确保LaTeX语法正确:行内数学用$...$,独立公式用$$...$$。 结构化回答: 1. 解释问题:私有成员(包括变量、函数或嵌套)不能直接从外部访问。 2. 在主函数中访问私有成员的方法: - 通过公共接口(如getter方法)。 - 通过友元函数或友元。 - 如果是嵌套,说明如何通过外部的公共方法访问。 3. 提供代码示例。 4. 生成相关问题。 参考引用: - 引用[1]、[2]、[3]强调私有访问限制。 - 引用[5]展示友元函数访问私有成员。 - 引用[4]关于静态成员。 可能的误解:用户说“私有声明”,可能不是嵌套,而是泛指私有成员。我会假设是访问私有成员的一般方法。 回答大纲: - 引言:解释私有成员的概念。 - 方法1:使用公共成员函数访问私有变量。 - 方法2:使用友元函数。 - 代码示例。 - 结论。 - 相关问题。 生成相关问题: - 基于主题,如友元的用法、嵌套等。 最终回答: </think>### C++主函数访问外部私有成员的方法C++中,私有成员(包括私有嵌套)设计为仅能在内部访问,以支持信息隐藏和封装原则\[^1\]\[^2\]\[^3\]。主函数(`main()`)作为外部代码,**不能直接访问其他私有成员或私有嵌套**,否则会导致编译错误。然而,可以通过以下间接方法实现访问: #### 1. **通过公共接口访问私有变量或函数** - 在中声明公共成员函数(如getter/setter),主函数通过这些接口间接访问私有数据。 - **原理**:公共成员函数在内部定义,因此可以访问私有成员;主函数调用这些公共函数即可。 - **示例代码**: ```cpp #include <iostream> using namespace std; class MyClass { private: int privateVar; // 私有成员变量 class InnerClass { // 私有嵌套声明 public: void display() { cout << "InnerClass accessed" << endl; } }; public: void setVar(int value) { privateVar = value; } // 公共setter int getVar() { return privateVar; } // 公共getter void useInner() { InnerClass innerObj; // 内部访问私有嵌套 innerObj.display(); } }; int main() { MyClass obj; obj.setVar(10); // 主函数通过公共接口设置私有变量 cout << "Private var: " << obj.getVar() << endl; // 输出: Private var: 10 obj.useInner(); // 间接访问私有嵌套方法 return 0; } ``` - 上述代码展示了主函数通过公共方法 `getVar()` 和 `useInner()` 间接操作私有成员\[^1\]\[^5\]。 #### 2. **通过友元函数访问私有成员** - 使用 `friend` 关键字声明友元函数或友元,允许外部函数访问私有成员。 - **原理**:友元函数被视为的一部分,可以访问所有私有成员\[^3\]\[^5\]。 - **示例代码**: ```cpp #include <iostream> using namespace std; class MyClass { private: int privateVar; class InnerClass { public: void display() { cout << "InnerClass via friend" << endl; } }; public: MyClass() : privateVar(5) {} friend void accessPrivate(MyClass& obj); // 声明友元函数 }; void accessPrivate(MyClass& obj) { cout << "Private var: " << obj.privateVar << endl; // 友元访问私有变量 MyClass::InnerClass innerObj; // 访问私有嵌套(需在作用域内) innerObj.display(); } int main() { MyClass obj; accessPrivate(obj); // 主函数调用友元函数 return 0; } ``` - 输出: ``` Private var: 5 InnerClass via friend ``` - **注意**:私有嵌套 `InnerClass` 只能在 `MyClass` 内部或其友元中直接实例化\[^2\]\[^5\]。 #### 3. **访问静态私有成员** - 静态私有成员可通过静态公共函数访问(如引用[4]所示)。 - **示例**:定义静态方法返回静态私有变量值。 ### 关键注意事项 - **直接访问限制**:主函数尝试直接访问私有成员(如 `obj.privateVar`)会触发编译错误:`error: 'privateVar' is private`\[^1\]\[^3\]。 - **设计原则**:优先使用公共接口而非友元,以维持封装性;友元适用于特定场景(如运算符重载)\[^5\]。 - **嵌套访问**:私有嵌套只能在声明它的内部或友元中使用,无法在主函数中直接声明(如 `MyClass::InnerClass inner;` 会失败)\[^2\]。 通过这些方法,可以实现主函数间接访问外部私有成员,同时遵守C++的封装规则\[^1\]\[^2\]\[^3\]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值