在不使用朋友的情况下访问私有成员 class

accessing private members without using friend class

我有一个家庭作业:

我们有这个代码:

#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 或使用朋友 class 的情况下,我们必须阅读 xy 并更改它们。

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

如果我的 class 中有一个 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. 我们能做些什么来防止这种事情发生吗?
  1. (with pointers) why this happen?

这个我没看懂,略过

  1. is such thing possible in other object oriented languages?

考虑 python。在 python 中,将某些内容设为私有显然只是作者与用户之间的协议,但没有什么可以阻止用户访问私有成员。虽然,他们不应该。 C++ 并没有那么明确地说 "if you want you can access private members",但仍然可以通过一些努力实现。然而你不应该。 C++ 不会阻止你搬起石头砸自己的脚,访问私有成员是一种方式。在您的示例中并非如此,但通常直接访问私有成员会破坏对象而无法修复。

  1. does this mean access modifiers are useless?

我再说一遍:红绿灯没用吗?我的意思是当它是红色的时候我仍然可以过马路。访问说明符并不是为了防止你做错事,它们是为了帮助你避免做错事(如果你努力尝试,你仍然可以做错事)。

  1. and is there anything we can do to prevent such thing to happen?

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

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

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

在 C++ 中使用 class 的代码通常能够看到 class 的内部构造,这确实是一个语言设计问题。可见的、完整的 class 定义显然违反了信息隐藏。这是必要的,因为 C++ 的 "by-value semantics" 继承自 C,并且与 C# 或 Java 区别开来。

结果之一就是您所描述的:用户可以更轻松地访问 object 他们并不反对的数据。 (公平地说,无论采取何种预防措施,如果有足够的恶意能量在一般意义上是无法预防的,但是了解 class 布局可以让你用更少的 "criminal effort" 来做到这一点,在这种情况下通过正常的语言手段.另一种更简单的方法,我记得在一个大型项目开源时埋没在它中,就是在包含有问题的 header 之前简单地 #define private public。)

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

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


1 在 1998 年左右的一次工作面试中,我作为 Star Division 的 C++ 开发人员正在开发 StarOffice,OpenOffice 和 LibreOffice 的原始前身,我被问到: "You have a base class, directly or indirectly used throughout the project. Now you would like to add a virtual function to it but avoid recompilation of the whole project, because it would just take too long. Can you do that? How?" 答案是大多数实现可能会在 vtable 中维护虚函数,您可以在不更改现有函数偏移量的情况下追加到该虚函数(当然,也不会更改 object 布局)。显然,无法保证实现不会向后生成 vtable,或采用其他机制,但实际上您可以这样做。