61

有没有办法(ab)使用C预处理器来模拟C中的命名空间?

我在想一些事情:

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

这将被翻译成:

name_of_ns_some_function() {
    name_of_ns_some_other_function();
}
4

10 回答 10

92

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

// 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_barand的多个定义my_baz并不冲突,因为它们是静态定义的,但仍然可以通过适当的命名空间结构访问底层函数。

于 2008-12-23T20:15:40.630 回答
58

使用命名空间前缀时,我通常会为缩短的名称添加宏,这些名称可以#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 描述的命名空间宏(在评论中)更干净、更有用的解决方案。

于 2008-12-23T21:44:07.630 回答
14

您可以使用 ## 运算符:

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

并将函数声明为:

void FUN_NAME(MyNamespace,HelloWorld)()

不过看起来挺别扭的。

于 2008-12-23T19:32:23.573 回答
10

我使用基于结构的方法,有两个改进:我添加子结构来创建分层命名空间,当我想简化命名空间的路径时,我定义了一些简单的宏。

我们以一个Foobar库为例。

foob​​ar.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__

foob​​ar.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代码文件:

foob​​ar_text.h

#ifndef __FOOBAR_TEXT_H__
#define __FOOBAR_TEXT_H__

void foobar_text__print(char *s);

#endif // __FOOBAR_TEXT_H__

foob​​ar_text.c

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

void foobar_text__print(char *s) {
    printf("%s\n", s);
}
于 2014-01-25T20:28:22.353 回答
8

我想出了以下方案:

(标题)

// 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
于 2011-03-10T22:30:15.320 回答
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();
于 2013-04-30T03:54:09.990 回答
4

我写了一篇关于如何使用 C 来利用命名空间和/或模板的教程。

C 中的命名空间和模板

C 中的命名空间和模板(使用链表)

对于基本命名空间,可以简单地为命名空间名称添加前缀作为约定。

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, ... );

我需要的第二种使用命名空间和模板概念的方法是使用宏连接和包含。例如,我可以创建一个

template<T> T multiply<T>( T x, T y ) { return x*y }

使用模板文件如下

乘法模板.h

_multiply_type_ _multiply_(multiply)( _multiply_type_ x, _multiply_type_ y);

乘法模板.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 }

我在提供的链接上创建了一个更详细的教程。希望这可以帮助某人!

于 2017-12-29T22:27:58.097 回答
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;
}

通过预处理器运行它会给你:

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;
}
于 2020-06-02T22:19:04.730 回答
0

这是一个构建上述方法的示例,并将它们组合用于函数和结构以创建伪命名空间 NAMESPACE1 和 NAMESPACE2。与拥有一个包含函数的结构相比,这样做的好处是,结构保持函数方法需要跨多个伪命名空间的标准化结构,这并不总是可能的(或者根本没有,或者没有很多可以说的工作不改进代码)或可取的。

不确定宏扩展顺序是否会成为问题,但这适用于 GCC,并且似乎最大限度地减少了所需的代码更改量,同时保持了不错的(尽管远非理想)可读性。


应用程序.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

header2.h 和 api2.c 中的其他代码与 header1.h 和 header2.h 相同,修改为命名空间“NAMESPACE2”

于 2014-01-04T21:04:25.873 回答
0

我意识到这是一个老问题(11 岁),但我试图完成我认为你最初想要的,正如你上面列出的那样。

我希望在我的函数前面有一个命名空间。但我希望能够改变那个命名空间。默认情况下,我希望这个示例没有命名空间,但如果发生命名冲突,那么我希望能够为库中的所有函数添加命名空间。(与默认情况下有命名空间的 C++ 相比,这稍有倒退,您可以使用它using namespace whatever来消除每次都指定命名空间的需要。)但是,就像 C++ 一样,如果您放入一个using namespace语句并为您的代码设置别名,您将需要更新您的呼叫代码。您也可以编写一些其他宏序列来自动重命名您的调用,但这超出了我认为您正在寻找的范围。

#include <stdio.h>

#define NAMESPACE(...) test_ //Use this as my prepender

//Where all the magic happens which could be included in a header file.
#ifndef NAMESPACE
//No Namespace by default
#define NAMESPACE(...)
#endif

//Actual replacements
#define NSPREPENDER(...) NSPROCESSING(NAMESPACE(), __VA_ARGS__)
#define NSPROCESSING(...) NSFINALIZE(__VA_ARGS__)
#define NSFINALIZE(a,b) a ## b


//BEGIN ACTUAL PROGRAM
//Prototype
void NSPREPENDER(myprint)();

int main()
{
    test_myprint(); //If NAMESPACE(...) is defined to anything else, this code must change.

    return 0;
}

//Implementation
void NSPREPENDER(myprint)()
{
    puts("Testing");
}

此代码将仅在 C99 及更高版本上编译,因为它使用可变参数宏。这些宏执行一种递归形式,所有这些都完成了,以便我们可以从顶部定义的宏中获取值。

其所有工作的细分:

  • 我们定义我们希望我们的命名空间是。
  • 如果未定义任何内容,请设置默认值
  • 做一堆调用来绕过和(ab)使用预处理器功能。
  • 将 NSPREPENDER 宏函数添加到每个 c 函数中,以便可以对其进行名称修改。
  • 使用重命名的名称编写代码,因为在编译器看到它时名称将被正确重整。

此代码已使用 clang 进行了测试。

于 2019-12-12T15:49:31.290 回答