【问题标题】:Namespaces in CC 中的命名空间
【发布时间】:2010-09-28 05:42:06
【问题描述】:

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

我的想法是这样的:

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

这将被翻译成:

name_of_ns_some_function() {
    name_of_ns_some_other_function();
}

【问题讨论】:

    标签: c++ c namespaces c-preprocessor


    【解决方案1】:

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

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

    【讨论】:

    • 我什至不会称其为 hack - 您正在使用语言功能组织暴露的功能部分。 +1 干净的解决方案!
    • C 有初始化声明?
    • avesus:是的。并且,在初始化时,任何未指定的结构成员都被初始化为 0。
    • 它可以工作,但不能部分指定结构
    • 多年前在这里看到我自己的 cmets 很有趣。但现在我带着负号来了。以这种方式未定义依赖声明的初始化顺序。该方法根本不适用于模块。
    【解决方案2】:

    当使用命名空间前缀时,我通常会为缩短的名称添加宏,可以在包含标题之前通过#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 描述的命名空间宏(在 cmets 中)更清洁、更有用。

    【讨论】:

    • 我喜欢这种方法,因为它保留了声明和调用函数的常用语法,而且还节省了我的打字时间。这可能是个人喜好问题,但我认为这是最易读的解决方案。
    【解决方案3】:

    您可以使用## 运算符:

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

    并将函数声明为:

    void FUN_NAME(MyNamespace,HelloWorld)()
    

    虽然看起来很尴尬。

    【讨论】:

    • 我认为 #define NS1(name) Namespace1 ## name 然后 void NS1(some_func)() 看起来不那么奇怪(实际上迫使你考虑你的命名空间)
    • 是的,我也在考虑一些“上下文”。但无论它是什么,这个想法都存在于## 运算符中。用户可以根据自己的需要对其进行调整。
    • 要小心连接运算符,因为您将它与其他宏一起使用。宏扩展的顺序没有严格定义,并且在我必须支持的两个编译器之间有所不同。可怕的是,它们都不是 GCC,所以我什至不知道它的作用。
    【解决方案4】:

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

    我们以 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);
    }
    

    【讨论】:

      【解决方案5】:

      我想出了以下方案:

      (标题)

      // 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
      

      【讨论】:

        【解决方案6】:

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

        // 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();
        

        【讨论】:

          【解决方案7】:

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

          Namespaces and templates in C

          Namespaces and templates in C (using Linked Lists)

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

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

          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 }
          

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

          【讨论】:

            【解决方案8】:

            您可以使用帮助程序#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;
            }
            

            【讨论】:

              【解决方案9】:

              这里是一个示例,它构建了上述方法并将它们组合用于函数和结构以创建伪命名空间 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”

              【讨论】:

                【解决方案10】:

                我意识到这是一个老问题(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 测试。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-12-23
                  • 2012-02-28
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多