在C语言中模拟访问限定符

5

在C语言中是否可能模拟C++的访问权限修饰符[public, private, protected]?更广义地说,C++编译器如何确保类的private成员不被非成员函数访问?


有什么意义呢?这些关键词用于在面向对象环境中指定方法行为。 - user849425
C++编译器读取代码并保留可见性列表。在C中模拟这个过程需要使用宏和预编译器,并思考在C程序中私有实际意味着什么。但这只是一个粗略的近似。 - Preet Sangha
@Michael:好奇心。由于可以使用函数指针和虚表实现C中的虚函数机制,从而模拟多态性,我想知道是否可能模拟封装性。 - Bandicoot
在C++中,访问说明符是类型系统的一部分。您将需要在C语言中模拟C++类型系统的这个方面。您可以使用哪种预处理器和链接器组合来完成这个任务取决于您。在此引用不透明类型是我个人认为有些牵强附会的。 - Kuba hasn't forgotten Monica
封装的问题在于它并不是真正的强制措施,而只是一种阻止和使访问成员更加模糊的方式。如果有人真的想要这样做,就能够违反C++的封装。正如人们所说的Python缺乏封装执行,“我们都已经长大了”。如果您将属性设计为私有的,那么直接使用它的人必须知道自己在做什么,并承担风险。 - lvella
3个回答

6

C++的访问控制完全是编译器的想象:你不能访问一个私有成员,只是因为编译器会拒绝编译任何试图这样做的代码。

事实上,通过欺骗编译器认为指向ClassWithPublicMember实例的指针实际上是指向ClassWithPrivateMember实例的指针,即通过使用稍微修改的头文件,通常可以访问C++类的私有成员。不过,没有人会做那样的事情...

在C语言中进行访问控制的最佳方法是传递指向不透明类型的指针:struct对象的定义对客户端代码不可用。如果提供foo* create_foo()方法和一系列操作foo*的方法,并将foo的实际定义隐藏在客户端之外,则可以实现类似的效果。

// File "foo_private.h"
struct foo {
    int private1;
    char private2;
};

// File "foo.h"
typedef struct foo foo;
foo * create_foo(int x, char y);
int mangle_foo(foo *);

// file "foo.c"
#include <stdlib.h>
#include "foo.h"
#include "foo_private.h"

foo * create_foo(int x, char y) {
    foo * f = (foo *) calloc(1, sizeof(foo));
    f->private1 = x;
    f->private2 = y;
}    

int mangle_foo(foo *f) {
    return f->private1 + f->private2;
}

现在,您可以将编译为库的foo.cfoo.h一起分发。在foo.h中声明的函数形成类型的公共接口,但该类型的内部结构是不透明的;实际上,调用create_foo()的客户端无法访问foo对象的私有成员。

我们的朋友FILE*也是类似的东西,只不过FILE类型通常并不是真正的不透明。只是大多数人(明智地)不会去研究它的内部结构。在那里,访问控制仅通过模糊性来强制执行。

谢谢您的回答。您能举个例子来说明“一系列操作void*的方法,将实现细节隐藏在客户端之外,这样就可以达到类似的效果”的意思吗? - Bandicoot
2
你看,这正是不透明类型的用途,因为 void * 对于正在进行的操作没有语义线索:typedef struct thing thing_t; 这样你就有了一个类型,它表示“内部无法维修”,但仍然提供了关于该类型应该执行的信息。 - tbert
1
void* 抛弃了类型安全性。有更好的方法来解决这个问题。 - blueshift
1
@blueshift — 我修改了我的示例以使用不透明类型。 - Ernest Friedman-Hill
似乎还有点混淆,typedef struct f foo;struct foo;(未使用?),而 create_foo 仍然返回 void。你测试过了吗..? - blueshift
@blueshift 嗯...不是。但我现在知道了。 - Ernest Friedman-Hill

4
我强烈不建议使用另一个答案中提到的void*指针(因为它会抛弃所有类型安全性),该指针可以通过在头文件中未指定其内容的方式前向声明struct foo,然后可以将这些结构体及其指针传入和传出在头文件中声明的接口函数。结构体实现被隐藏在该单元的.c文件中。
如果您希望保留在结构体和其他类型(例如int)之间进行更改的选项,则可以在头文件中使用typedef包装接口的类型。
您可以使用在.c文件中声明static的函数等其他技术,这样即使其他来源声明该函数也无法从中链接。

1

有很多方法可以实现目标,以下是我的方法:

示例包括一个类“struct test_t”,一个类函数“test_create”和一个成员函数“print”

test.h:

struct test_t {
    // Member functions
    void (*print)(struct test_t *thiz);

    // Private attributes
    char priv[0];
};


// Class functions
struct test_t *test_create(int number);

test.c:

#include "test.h"
#include <stdio.h>
#include <stdlib.h>

// priv attr
struct test_priv_t {
    int number;
};


// member functions
static void print(struct test_t *thiz)
{
    struct test_priv_t *priv = (struct test_priv_t*)thiz->priv;
    printf("number = %d\n", priv->number);
}


// Class functions
struct test_t *test_create(int number)
{
    struct test_t *test = (struct test_t *)malloc(sizeof(struct test_t) + sizeof(struct test_priv_t));

    // setup member function
    test->print = print;

    // initialize some priv attr
    struct test_priv_t *priv = (struct test_priv_t*)test->priv;
    priv->number = number;

    return test;
}

main.c:

#include "test.h"

int main()
{
    struct test_t *test = test_create(10);
    test->print(test);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接