C中的命名空间

64

有没有一种方法可以(滥用)C预处理器来在C中模拟命名空间?

我想的是这样的:

#define NAMESPACE name_of_ns
some_function() {
    some_other_function();
}

这将被翻译为:

name_of_ns_some_function() {
    name_of_ns_some_other_function();
}
11个回答

94

另一种选择是声明一个结构体来保存所有函数,然后静态地定义函数。这样,您只需要担心全局名称结构体的名称冲突。

// foo.h
#ifndef FOO_H
#define FOO_H
typedef struct { 
  int (* const bar)(int, char *);
  void (* const baz)(void);
} namespace_struct;
extern namespace_struct const foo;
#endif // FOO_H

// foo.c
#include "foo.h"
static int my_bar(int a, char * s) { /* ... */ }
static void my_baz(void) { /* ... */ }
namespace_struct const foo = { my_bar, my_baz }

// main.c
#include <stdio.h>
#include "foo.h"
int main(void) {
  foo.baz();
  printf("%d", foo.bar(3, "hello"));
  return 0;
}
在上面的例子中,my_barmy_baz不能直接从main.c中调用,只能通过foo间接调用。
如果您有一堆声明具有相同签名的函数的命名空间,那么您可以为该集合标准化您的命名空间结构,并在运行时选择要使用的命名空间。
// goo.h
#ifndef GOO_H
#define GOO_H
#include "foo.h"
extern namespace_struct const goo;
#endif // GOO_H

// goo.c
#include "goo.h"
static int my_bar(int a, char * s) { /* ... */ }
static void my_baz(void) { /* ... */ }
namespace_struct const goo = { my_bar, my_baz };

// other_main.c
#include <stdio.h>
#include "foo.h"
#include "goo.h"
int main(int argc, char** argv) {
  namespace_struct const * const xoo = (argc > 1 ? foo : goo);
  xoo->baz();
  printf("%d", xoo->bar(3, "hello"));
  return 0;
}

my_barmy_baz 的多个定义不会产生冲突,因为它们是静态定义的,但底层函数仍可通过适当的命名空间结构访问。


6
我不认为这算作黑客行为——你只是利用一种语言特性来组织已经暴露的功能。对于这样一个干净利落的解决方案,点赞! - user1898811
1
C语言中可以在声明时进行初始化吗? - Brian Cannard
2
avesus:是的。而且,当初始化时,任何未指定的结构成员都会被初始化为0。 - rampion
它可能可以工作,但结构体不能部分指定。 - mihaipopescu
看到我几年前的评论还是很有趣的。但现在我带着双减号来了。依赖声明的初始化顺序以这种方式未定义。该方法对模块根本不起作用。 - Brian Cannard
@BrianCannard C 有模块吗? - SO_fix_the_vote_sorting_bug

60

当使用命名空间前缀时,我通常会添加宏来缩短名称,可以通过在头文件包含之前激活 #define NAMESPACE_SHORT_NAMES 来实现。一个名为 foobar.h 的头文件可能如下所示:

// inclusion guard
#ifndef FOOBAR_H_
#define FOOBAR_H_

// long names
void foobar_some_func(int);
void foobar_other_func();

// short names
#ifdef FOOBAR_SHORT_NAMES
#define some_func(...) foobar_some_func(__VA_ARGS__)
#define other_func(...) foobar_other_func(__VA_ARGS__)
#endif

#endif

如果我想在一个包含文件中使用短名称,我会这样做:

#define FOOBAR_SHORT_NAMES
#include "foobar.h"

我认为这比使用Vinko Vrsalovic所描述的命名空间宏更加简洁且有用。


我喜欢这种方法,因为它保留了声明和调用函数的常规语法,同时也节省了一些打字。这可能是个人偏好的问题,但我认为这是最易读的解决方案。 - Kim Stebel
3
我建议使用更简单的宏定义:#define some_func foobar_some_func。这样可以使用短名称来初始化函数指针。 - tstanisl

14

您可以使用 ## 运算符:

#define FUN_NAME(namespace,name) namespace ## name

并将函数声明为:

void FUN_NAME(MyNamespace,HelloWorld)()

看起来相当尴尬。


1
我认为 #define NS1(name) Namespace1 ## name 以及 void NS1(some_func)() 看起来不那么奇怪(实际上可以促使你更深入地思考命名空间)。 - Vinko Vrsalovic
是的,我也在考虑一些“上下文”。但无论如何,这个想法都在##运算符内。由用户根据自己的需求进行适应。 - Mehrdad Afshari
如果您正在将连接运算符与其他宏一起使用,请小心。宏扩展的顺序没有严格定义,并且在我必须支持的两个编译器之间存在差异。令人恐惧的是,它们中没有一个是 GCC,所以我甚至不知道它的行为如何。 - Phil Miller

11

我采用基于结构体的方法,进行了两次精简:我添加了子结构以创建分层命名空间,并在需要简化命名空间路径时定义了一些简单的宏。

我们以Foobar库为例。

foobar.h

#ifndef __FOOBAR_H__
#define __FOOBAR_H__

// definition of the namespace's hierarchical structure
struct _foobar_namespace {
    struct {
        void (*print)(char *s);
    } text;
    struct {
        char *(*getDateString)(void);
    } date;
};

// see the foobar.c file
// it must be the only one defining the FOOBAR macro
# ifndef FOOBAR
    // definition of the namespace global variable
    extern struct _foobar_namespace foobar;
# endif // FOOBAR

#endif // __FOOBAR_H__

foobar.c

// the FOOBAR macro is needed to avoid the
// extern foobar variable declaration
#define FOOBAR

#include "foobar.h"
#include "foobar_text.h"
#include "foobar_date.h"

// creation of the namespace global variable
struct _foobar_namespace foobar = {
    .text = {
        .print = foobar_text__print
    },
    .date = {
        .getDateString = foobar_date__getDateString
    }
};

然后,可以使用命名空间:

#include "foobar.h"

void main() {
    foobar.text.print("it works");
}

但是foobar_text__print()foobar.text.print()之间的区别并不大。我认为第二种写法更易读,但这是有争议的。因此,通过定义一些宏来简化这些命名空间,可以使其变得非常有用:

#include "foobar.h"

#define txt    foobar.text
#define date   foobar.date

void main() {
    char *today = date.getDateString();
    txt.print(today);
}

这种分层命名空间定义快速、易于理解,可以减少代码冗长。


只是为了好玩,这里是 foobar.text 代码的文件:

foobar_text.h

#ifndef __FOOBAR_TEXT_H__
#define __FOOBAR_TEXT_H__

void foobar_text__print(char *s);

#endif // __FOOBAR_TEXT_H__

foobar_text.c

#include <stdio.h>
#include "foobar_text.h"

void foobar_text__print(char *s) {
    printf("%s\n", s);
}

9

I came up with the following scheme :

(header)

// NS_PREFIX controls the prefix of each type and function declared in this
// header, in order to avoid name collision.
#define NS_PREFIX myprefix_

// Makes a string from argument (argument is not macro-expanded).
#define stringify(arg) #arg

// Concatenation that macro-expands its arguments.
#define concat(p1, p2) _concat(p1, p2) // Macro expands the arguments.
#define _concat(p1, p2) p1 ## p2       // Do the actual concatenation.

// Append the namespace prefix to the identifier.
#define ns(iden) concat(NS_PREFIX, iden)

// header content, for instance :
void ns(my_function)(int arg1, ns(t) arg2, int arg3);

// Allow implementation files to use namespacing features, else
// hide them from the including files.
#ifndef _IMPL
#undef NS_PREFIX
#undef ns
#undef stringify
#undef concat
#undef _concat
#endif // _IMPL

(实现)
#define  _IMPL 
#include "header.h"
#undef   __IMPL

5
我写了一篇关于如何使用C语言的命名空间和/或模板的教程。对于基本的命名空间,可以使用前缀作为约定来实现。以下是两篇相关文章的链接:
C语言中的命名空间和模板
使用链表的C语言中的命名空间和模板请注意,保留html标记。
namespace MY_OBJECT {
  struct HANDLE;
  HANDLE *init();
  void destroy(HANDLE * & h);

  void do_something(HANDLE *h, ... );
}

可以写成:
struct MY_OBJECT_HANDLE;
struct MY_OBJECT_HANDLE *my_object_init();
void my_object_destroy( MY_OBJECT_HANDLE * & h );

void my_object_do_something(MY_OBJECT_HANDLE *h, ... );

第二种方法是使用命名空间和模板的概念,使用宏拼接和include。例如,我可以创建一个包含所有需要的头文件和命名空间声明的头文件,然后在需要这些声明的地方包含该头文件。这样,我就可以避免在每个文件中都需要单独声明相同的命名空间。同时,我还可以使用宏拼接来简化代码并使其更易维护。
template<T> T multiply<T>( T x, T y ) { return x*y }

使用以下模板文件:
multiply-template.h
_multiply_type_ _multiply_(multiply)( _multiply_type_ x, _multiply_type_ y);

multiply-template.c

_multiply_type_ _multiply_(multiply)( _multiply_type_ x, _multiply_type_ y) {
  return x*y;
}

我们现在可以定义int_multiply函数,如下所示。在这个例子中,我将创建一个int_multiply.h/.c文件。
int_multiply.h:
#ifndef _INT_MULTIPLY_H
#define _INT_MULTIPLY_H

#ifdef _multiply_
#undef _multiply_
#endif
#define _multiply_(NAME) int ## _ ## NAME 

#ifdef _multiply_type_
#undef _multiply_type_
#endif
#define _multiply_type_ int 

#include "multiply-template.h" 
#endif

int_multiply.c

#include "int_multiply.h"
#include "multiply-template.c"

在这一切结束时,您将拥有一个函数和头文件。
int int_multiply( int x, int y ) { return x * y }

我已经为提供的链接创建了一个更详细的教程。希望这对某些人有所帮助!


1
这很有趣。但链接已经失效了。你把页面移到其他地方了吗?谢谢。 - user1596274

4

一个类似于被接受答案的方法如下:

// inclusion guard
#ifndef FOOBAR_H_
#define FOOBAR_H_

// long names
void foobar_some_func(int);
void foobar_other_func();

// qualified names
#ifdef FOOBAR_SHORT_NAMES
extern struct _foobar {
     void (*some_func)(int);
     void (*other_func)();
} foobar;
#endif

#endif

这个头文件应该和一个.c文件一起使用:
#include "foobar.h"
struct _foobar foobar = {
    foobar_some_func;
    foobar_other_func;
};

使用这些函数时,

foobar.some_func(10);
foobar.other_func();

不错,这闻起来像是 C++。为什么不把长名称的声明移到 C 文件中呢? - grenix

1

可以使用前缀来命名文件函数,就像在C / C ++的stb单文件公共域库中一样:“作为一种相对合理的方式来对文件名和源函数名进行命名空间管理”。

例如:

  • stb_image_write.h(文件名)
  • STBI_THREAD_LOCAL(名称)
  • static void *stbi__malloc_mad4(int a, int b, int c, int d, int add)

我想知道为什么人们喜欢 . 而不是 _,而避免使用这个简单的解决方案。 - SO_fix_the_vote_sorting_bug

1
您可以使用一个帮助者 #define 宏:
#include <stdio.h>

#define ns(x) gargantua_ ## x

struct ns(stats) {
    int size;
};

int ns(get_size)(struct ns(stats) *st) {
    return st->size;
}

void ns(set_size)(struct ns(stats) *st, int sz) {
    st->size = sz;
}

int main(void) {
    struct ns(stats) stats = {0};

    ns(set_size)(&stats, 3);
    printf("size=%d\n", ns(get_size)(&stats));
    return 0;
}

Running it through the preprocessor gives you:

struct gargantua_stats {
    int size;
};

int gargantua_get_size(struct gargantua_stats *st) {
    return st->size;
}

void gargantua_set_size(struct gargantua_stats *st, int sz) {
    st->size = sz;
}

int main(void) {
    struct gargantua_stats stats = {0};

    gargantua_set_size(&stats, 3);
    printf("size=%d\n", gargantua_get_size(&stats));
    return 0;
}

0

以下是一个示例,基于以上方法结合了函数和结构体,以创建伪命名空间NAMESPACE1和NAMESPACE2。与持有函数的结构体相比,这种方法的好处在于需要在多个伪命名空间中使用标准化结构的情况并不总是可能(要么根本不可能,要么需要大量工作,这显然不能改进代码)。

不确定宏扩展顺序是否会成为问题,但这在GCC上可以工作,并且似乎最小化了所需的代码更改,同时保持了相当不错的可读性(尽管远非理想)。


application.c:

#include <stdio.h>
#include "header1.h"
#include "header2.h"

/* use NAMESPACE1 and NAMESPACE2 macros to choose namespace */

int main() {
  NAMESPACE1(mystruct) data1; // structure specific to this namespace
  NAMESPACE2(mystruct) data2; 

  data1.n1 = '1';
  data1.c  = 'a';
  data2.n2 = '2';
  data2.c  = 'a';

  NAMESPACE1(print_struct)(&data1); // function specific to this namespace
  NAMESPACE2(print_struct)(&data2);

}

header1.h

/* the below block is unnecessary, but gets rid of some compiler warnings */
#ifdef NAMESPACE_REAL
#undef NAMESPACE_REAL
#endif

/* edit the below lines to change the three occurrences of NAMESPACE1 to the desired namespace */
#define NAMESPACE1(name) NAMESPACE1 ## _ ## name
#define NAMESPACE_REAL(name) NAMESPACE1(name)


/* don't edit the next block */
#define TYPEDEF(name, ...) typedef struct NAMESPACE_REAL(name) { __VA_ARGS__ } NAMESPACE_REAL(name)
#define STRUCT(name) struct NAMESPACE_REAL(name)
#define FUNC(name) NAMESPACE_REAL(name)

/* normal header code, using FUNC and STRUCT macros */
#include <stdio.h>

TYPEDEF(mystruct,
        char n1;
        char c;
        );

void FUNC(print_struct)(STRUCT(mystruct) *data);

/* don't edit the rest */
#undef TYPEDEF

api1.c:

#include "header1.h"

/* normal code, using FUNC and STRUCT macros */
void FUNC(print_struct)(STRUCT(mystruct) *data) {
  printf("this is the struct from namespace1: %c %c\n", data->n1, data->c);
}


/* don't edit the rest */
#undef STRUCT
#undef FUNC
#undef NAMESPACE
#undef NAMESPACE_REAL

和中的其他代码与和相同,只是针对命名空间“NAMESPACE2”进行了修改。


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