7

我想访问一个类中的私有数据成员。类中没有访问私有数据成员的成员函数。它是私人的。

我想上课和一些如何破解它。一种方法是复制类的声明,将私有成员设为公有并调用新类类 something_else。然后我重新解释演员并复制原始对象。这行得通。但我想要更优雅的东西......或者可能是通用的......或者只是另一种方式。

有哪些选择?我可以使用 void* 吗?我可以把这个班级memcpy到另一个空班吗?有什么方法可以做到这一点?

%

4

6 回答 6

19

我假设

  1. 你已经经历过“打破封装不好”的阶段,
  2. 用尽了其他可能的解决方案,
  3. 无法更改类的标题。

有几种方法可以破坏对类的私有成员的访问,如GotW #76 所示

  1. 复制一个类定义并添加一个friend声明。
  2. 使用邪恶的宏:#define private public 在包含类的 header 之前
  3. 编写具有相同二进制布局的类定义,并用于reinterpret_cast从原始类切换到假类。
  4. 如果有一个模板成员函数(唯一的可移植解决方案),则专门化一个模板成员函数。
于 2009-10-03T08:05:02.793 回答
3

使用您在问题中建议的想法,您无需复制原始对象。如果您编写自己的真实类声明的“所有公共”变体,然后将指针转换为该新类型,则可以通过它直接访问对象。

这一切都不是一个好主意的原因很简单。您必须操作您不控制其源的类的对象(否则您可以修改源以提供所需的访问权限)。但是如果你不控制源代码,那么如果维护者改变了他们的类的布局呢?您的重复版本将不再匹配,编译器将无法检测到这种不匹配。结果可能是运行时内存损坏。

于 2009-10-03T08:21:32.947 回答
3

由于理解有误,我必须澄清一下。以下所有解决方案都不需要您重新编译对象。要在您的代码中使用一个类,如果它被编译成一个目标文件,您应该在该类的声明中包含头文件。

#include <class.h>
ObjectFoo instance;

可以(但危险,除非您小心)更改标头 (a) 或将标头复制到另一个位置并包含该标头 (b),而无需重新编译类本身

#include <class_fixed.h>
ObjectFoo instance;

您在其中包含新标头的代码只会认为在目标文件中(您尚未重新编译!)他会找到声明为 in 的类的实现class_fixed.h。虽然仍然存在声明为 in 的类class.h。如果您在新标头中更改成员的偏移量(例如添加新成员),您就死定了,代码将无法正常工作。但只需更改访问权限即可。编译后的代码不知道访问,这仅在编译奇怪时才重要。

这并不总是有害的。在日常生活中,当您将新版本的库安装到系统中并且不重新编译所有依赖它的程序时,您会遇到这样的变化。但是应该小心处理


有几种解决方案。

  1. memcpy()
    不!不要 memcpy,因为对象复制有时会受到类设计者强加的特定策略。例如,auto_ptrs不能只是 memcopyed:如果你 memcopy theauto_ptr然后为两者运行析构函数,你将尝试释放相同的内存两次,程序将崩溃。

  2. 更改private:public:in header 或 with macro
    如果您的许可证允许,您可以通过编辑类实现附带的头文件来解决您的问题。实现的源代码(即类的 cpp 文件)是否在您的控制之下并不重要:将数据成员(在标头中)的私有更改为公共就足够了,并且即使您得到一个二进制文件也可以正常工作 -唯一包含类定义的库。(对于成员函数,更改访问权限有时会更改其内部名称,但对于 MSVS 和 GCC 是可以的。)

  3. 添加新的 getter 函数
    虽然更改privatepublic几乎总是可以的(除非您依赖特定的编译时检查,如果类具有某些可访问的成员会中断编译),添加新的 getter 函数应该小心执行。getter 函数应该是内联的(因此在类的头文件中定义)。

  4. reinterpret_cast
    如果您没有将指针转换为指向动态基类的指针(动态意味着“带有虚函数或基”),那么转换就可以正常工作,其在转换时的实际实例可以从特定代码段的类派生。

  5. protected:
    以防万一你忘记了。C++ 可以声明成员protected:,即只能对从给定派生的类进行访问。这可能会满足您的需求。

于 2009-10-03T08:23:40.387 回答
1

你可以,但你不应该。对象只是记忆。您当然可以将指针转换为具有相同成员但所有内容都是公共的等效类。但是你为什么要这样做呢?你有其他人的代码需要使用吗?让他们添加适当的访问器方法。您真的需要将他们视为公共成员吗?改变班级。

我不确定您要做什么,但这可能是一个错误。

于 2009-10-03T08:05:55.427 回答
0

谢谢...我确实想展示我最初修复的代码。有人提到的原因是我无法更改原始代码......所以我必须越狱。


#include<iostream>
using namespace std;

// Class Objectfoo
// Pretend Objectfoo lives somewhere else ... I cannot open him up

class ObjectFoo 
{
  private:
  int datax; 
  public:
   ObjectFoo() { datax = 100; }
   void get() { cout << datax << endl;}
};

// Class ObjectBar
class ObjectBar 
{
  public:
   int datax;
};

ObjectFoo FOOEY;

ObjectBar* touch_foo(int x, ObjectFoo* foo , ObjectBar* bar)
{
 bar = reinterpret_cast<ObjectBar*>(foo);
 bar->datax = x;
 return bar;
}

int main() 
{
  ObjectBar* bar;

  cout << "Displaying private member in ObjectFoo i.e. ObjectFoo.datax" << endl;
  FOOEY.get();

  cout << "Changing private member " << endl;
  bar = touch_foo(5, &FOOEY, bar);

  cout << "bar->datax = " << bar->datax << endl;

  cout << "Displaying private member in ObjectFoo i.e. ObjectFoo.datax" << endl;
  FOOEY.get();

  return 0;
}

这行得通……但我想我想要更通用的……或更灵活的东西。

%

于 2009-10-03T08:51:10.587 回答
0

我同意“编辑源代码”的评论,但我认为你应该添加一个方法,而不仅仅是注释掉“私人”。

您必须拥有类的声明,因此您可能拥有标题但可能没有 .cpp/whatever 文件。在头文件的副本中向类添加内联成员函数,并包含此头文件而不是原始头文件。您仍然应该能够链接到无法访问的源代码的目标文件。

当然,这算作一种邪恶的黑客攻击,绕过了语言内置的保护措施,而不是使用它们。这就是为什么我建议最低限度的邪恶黑客 - 不要将所有内容都设为私有,如果你可以使用 getter(但没有 setter)就可以做到这一点。当然,真正最小的邪恶是不要去做,如果有任何办法可以避免的话。

请记住,如果这是您正在使用的其他人的类,则下一个版本可能会以不同的方式实现,并且可能根本没有该成员。

于 2009-10-03T08:18:19.663 回答