【问题描述】:

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

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

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

%

【问题讨论】:

  • 编辑源代码怎么样? //private: 可以正常工作;)
  • 我认为他想让他的班级的用户不要乱来(所以是私人的),并且需要实现一些不适合当前设计的东西。
  • 我认为你没有代码,不是吗?
  • @LiraNuna:评论 'private:' 标签在某些情况下会起作用,但在其他情况下可能会巧妙地破坏代码。您可以巧妙地将非 POD 类型更改为 POD 类型,语言对这些类型的规则略有不同。
  • 我想我们需要知道为什么你想要这个。答案很大程度上取决于您要达到的目标。

标签: c++ private member


【解答1】:

我假设

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

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

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

【问题讨论】:

  • 喜欢提到邪恶的宏 ;)
【解答2】:

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

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

【问题讨论】:

    【解答3】:

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

    #include <class.h>
    ObjectFoo instance;
    

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

    #include <class_fixed.h>
    ObjectFoo instance;
    

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

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


    有几种解决方案。

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

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

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

    4. reinterpret_cast
      如果您不强制转换指向动态基类的指针(动态表示“具有虚函数或基”),其在转换时的实际实例可以从特定代码段的类派生。

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

    【问题讨论】:

      【解答4】:

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

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

      【问题讨论】:

        【解答5】:

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

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

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

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

        【问题讨论】:

          【解答6】:

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


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

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

          %

          【问题讨论】:

          • 这里的许多答案都假设您无法更改原始 .cpp 代码,而您可以更改 - 或只是复制、重命名和重新包含 - 标头 文件。用这种洞察力重新阅读答案。
          • 最好将此信息添加到原始问题中,而不是将其添加为答案。