【问题标题】:accessing private members without using friend class [duplicate]在不使用朋友类的情况下访问私人成员[重复]
【发布时间】:2020-09-24 10:31:57
【问题描述】:

我有一个家庭作业:

我们有这个代码:

#include <iostream>
using namespace std;
class Test {
    int x;
    char y;
public:
    Test() :x(0), y(0) { ; }
};
int main() {
    Test t;
    //Do stuff!
    return 0;
}

在不添加 getter 和 setter 或使用朋友类的情况下,我们必须阅读 x 和 y 并更改它们。

我搜索并找到了这些方法:

如果我的课堂上有 template 函数,我可以说:

class Test {
    int x;
    char y;
public:
    Test() :x(0), y(0) { ; }
    template<typename T>
    void do_something() {//not necessarily void function
        //Do some stuff 
    };
};

class a;
// My specialization.
template <>
void Test::do_something<a>() {
    cout << x << endl;
    cout << y << endl;
    // getting data 
    x = 5;
    y = 'a';
    // changing data
    cout << x << endl;
    cout << y << endl;
    // getting data after changes we made
}

int main() {
    Test t;
    t.do_something<a>();
    return 0;
}

我认为是这个问题的答案的方法也是使用指针。

像这样:

class Test {
    int x;
    char y;
public:
    Test() :x(0), y('0') { ; }
};
int main() {
    Test t;
    int* ptr = (int*)&t;
    cout << "x = " << *ptr << " y = " << (char)*(ptr + 1) << endl;
    *ptr--;
    //getting data
    *ptr = 12;
    ptr++;
    *ptr = 65;
    //changing data
    ptr--;
    cout << "x = " << *ptr << " y = " << (char)*(ptr + 1) << endl;
    //getting data after changes we have made
    return 0;
}

或使用reinterpret_cast 和指针:

struct pointer {
    int x;
    char y;
};

class Test {
    int x;
    char y;
public:
    Test() :x(0), y('0') { ; }
};
int main() 
{
    Test t;

    pointer* p = reinterpret_cast<pointer*>(&t);
    cout << "X = " << p->x << " Y = " << p->y << endl;
    //getting data
    p->x = 5;
    p->y = 'a';
    //changing data
    cout << "X = " << p->x << " Y = " << p->y << endl;

    //getting data from class after changing them with pointers
    return 0;
}

我的问题是:

  1. 在其他面向对象的语言中是否有可能?
  2. 这是否意味着访问修饰符无用?
  3. 我们能做些什么来防止这种事情发生吗?

【问题讨论】:

  • 问题不清楚。你说你不能修改Test,但你所有的尝试都会修改Test。如果允许您修改它,那么您应该编写 getter 和 setter(没有模板,绝对不是 reinterpret_cast)
  • 红绿灯没用吗?我的意思是当它是红色时我仍然可以过马路......访问说明符不是为了防止你做错事,他们是为了帮助你避免做错事(如果你努力你仍然可以做错事)
  • 如果可以将do_something()添加到Test,则可以添加get_x()、get_y()、set_x()和set_y()。
  • 值得注意的是,第二种方法是未定义的。该语言不允许将 Test 指针转换为 int 指针,然后取消引用该指针。
  • 基本上,这应该可以工作:Test t; pointer p; memcpy(&amp;p, &amp;t, sizeof(Test)); p.x = 1; p.y = 1; memcpy(&amp;t, &amp;p, sizeof(Test));。

标签: c++ pointers private encapsulation access-modifiers


【解决方案1】:

回答您的问题 4,我们可以做些什么来防止这种事情发生吗?:

在 C++ 中使用类的代码通常能够看到类的内部构成,这确实是一个语言设计问题。一个可见的、完整的类定义显然违反了信息隐藏。但这是必要的,因为 C++ 的“按值语义”继承自 C 并将其与 C# 或 Java 区分开来。

您所描述的结果之一是:用户可以更轻松地访问他们不反对的对象数据。 (公平地说,有足够的恶意能量,无论采取何种预防措施,在一般意义上都是无法预防的,但知道班级布局可以让你以更少的“犯罪努力”做到这一点,在这种情况下通过正常的语言手段。另一个,甚至更简单我记得当它开源时被埋在一个大型项目中的方式是在包含有问题的标题之前简单地#define private public。)

第二个更相关的问题是,使用该类或其后代对象的代码与该类的耦合过于紧密;它知道的比它应该或需要的多。对类的任何微不足道的更改都需要直接或间接地重新编译包含其定义的所有代码。对于涉及基类的复杂类层次结构的大型项目,可能会导致整个项目的重新构建毫无意义。1

最后回答您的问题:减少这种耦合的规范 C++ 策略是compilation firewalls。你本质上定义了一个纯虚函数的接口,没有数据,应该是比较稳定的。用户代码只能看到那个界面。通过它,您可以获得信息隐藏和多态性的力量。但是因为您不能再直接处理对象而只能处理指针或引用,所以您失去了 C++ 的按值范式的优势(速度、无别名)。


1 在 1998 年左右作为 C++ 开发人员在 Star Division 的一次工作面试中,当时我正在开发 OpenOffice 和 LibreOffice 的前身 StarOffice,我被问到:“你有一个基类,在整个项目中直接或间接使用。现在你想给它添加一个虚函数,但避免重新编译整个项目,因为它会花费太长时间。你能做到吗?怎么做?答案是大多数实现可能会在一个 vtable 中维护虚函数,您可以在不更改现有函数的偏移量的情况下附加到该 vtable 中(当然,也无需更改对象布局)。显然,不能保证实现不会向后生成 vtable,或采用其他机制,但实际上您可以这样做。

【讨论】:

  • 谢谢,你解释的真好,有一段时间了,因为我学习了纯虚函数,对我来说不是很清楚,你的回答很有帮助。
【解决方案2】:
  1. (带指针)为什么会这样?

这个我看不懂,就跳过了。

  1. 在其他面向对象的语言中是否有可能?

考虑蟒蛇。在 python 中,将某些东西设为私有只是作者和用户之间的协议,但没有什么能阻止用户访问私有成员。虽然,他们不应该。 C++ 并没有那么明确地说“如果你愿意,你可以访问私有成员”,但仍然可以通过一些努力。不过你不应该。 C++ 并不会阻止你在自己的脚下开枪,而访问私人成员是这样做的一种方式。在您的示例中并非如此,但通常直接访问私有成员会使对象无法修复。

  1. 这是否意味着访问修饰符无用?

我会重复我的评论:红绿灯没用吗?我的意思是当它是红色时,我仍然可以过马路。访问说明符并不是为了防止你做错事,而是帮助你避免做错事(如果你努力,你仍然可以做错事)。

  1. 我们能做些什么来防止这种事情发生吗?

将成员声明为私有足以表明用户不应以任何方式直接访问该成员。如果有人想违反该协议,那么他们可以做到。您无法阻止用户做错事。如果他们想打破你的班级,他们可以这样做。但是,保证损坏的东西仍然按预期工作不是您的责任。如果用户绕过访问说明符,则他们违反了他们与您之间的协议。考虑一下你买了一台笔记本电脑,然后从 42 楼把它扔出窗外。您会向制造商抱怨笔记本电脑之后无法正常工作吗?我想不会,相反你会明白你在使用笔记本电脑时出了点问题。

PS:您的最后两个示例是未定义的行为。 reinterpret_cast 不是一种在任意类型之间转换的神奇方式。实际上,允许的强制转换集以及您可以对结果做什么是相当有限的(请参阅here)。此外,c 风格的转换使您可以对可能非常错误的转换进行处理,而不会让编译器抱怨它。这就是为什么应该避免使用正确的 c++ 强制转换 (static_cast et al)。

【讨论】:

  • 谢谢,你解释得很好。这个作业我也不是很清楚,但是当我问到它时,我被告知在互联网上搜索问题标题,我找到了这些代码。我还有另一个问题是使用memcpy,正如评论中提到的UB?
  • @hanie 不,使用memcpy 很好,没有你的指针的问题是你假装有一个pointer 对象但没有(你从未创建过)。只有填充可能会成为问题
  • 我有一个问题。我在这里了解了使用reinterpret_cast:stackoverflow.com/a/8282638/12330258。是它也错了还是我做错了?
  • @hanie 代码可以使用特定的编译器,但就 C++ 标准而言,它不是有效的 C++。
猜你喜欢
  • 2015-07-02
  • 1970-01-01
  • 2021-08-16
  • 1970-01-01
  • 2013-04-09
  • 1970-01-01
  • 2015-08-03
  • 2022-11-13
  • 1970-01-01
相关资源
最近更新 更多