将命名空间添加到具有C头的C ++实现中

时间:2011-11-14 00:24:41

标签: c++ c namespaces

我们有一个包含C和C ++代码的大型项目。

对于每个C ++实现,除了C ++标头之外,我们通常还提供一个C-header,以便为.c文件提供功能。

因此,我们的大多数文件都是这样的:

foo.hpp:

class C { 
    int foo();
};

foo.h中:

#ifdef __cplusplus
extern "C" {
    typedef struct C C;  // forward declarations
#else
    class C;
#endif

    int foo( C* );               // simply exposes a member function
    C*  utility_function( C* );  // some functionality *not* in foo.hpp  

#ifdef __cplusplus
}
#endif

Foo.cpp中:

int C::foo()  { /* implementation here...*/ }

extern "C"
int foo( C *p ) { return p->foo(); }

extern "C"
C*  utility_function ( C* ) { /* implementation here...*/ }

问题:

假设我想像这样添加一个名称空间:

foo.hpp:

namespace NS {
    class C { 
        int foo();
    };
}

C-header中最好的方案是什么?

我考虑了一些选择,但我正在寻找最优雅,安全且易于阅读的选项。您使用的是标准方式吗?


以下是我考虑的选项:  (为简单起见,我省略了extern "C"构造)

  • 选项1:通过在每个标头中添加一些代码来欺骗编译器:

foo.h中

#ifdef __cplusplus
    namespace NS { class C; }  // forward declaration for C++ 
    typedef NS::C NS_C;
#else
    struct NS_C;  // forward declaration for C
#endif

int foo( NS_C* );
NS_C*  utility_function( NS_C* );

这增加了标头的一些复杂性,但保持实现不变。


  • 选项2:使用C-struct包装命名空间:

    保持标题简单但使实现更复杂:

foo.h中

struct NS_C;  // forward declaration of wrapper (both for C++ and C)

int foo( NS_C* );
NS_C*  utility_function( NS_C* );

Foo.cpp中

namespace NS {
    int C::foo() { /* same code here */ }
}

struct NS_C {     /* the wrapper */
    NS::C *ptr;
};

extern "C" 
int foo( NS_C *p ) { return p->ptr->foo(); }

extern "C"
NS_C *utility_function( NS_C *src ) 
{
    NS_C *out = malloc( sizeof( NS_C ) );  // one extra malloc for the wrapper here...
    out->ptr = new NS::C( src->ptr );
    ...
}

这些是唯一的方案吗?这些中有任何隐藏的缺点吗​​?

3 个答案:

答案 0 :(得分:2)

我发现更容易以某种方式对代码进行分解,以便foo.h仅包含C ++细节的最低限度,而foo.hpp处理粗糙的位。

文件 foo.h 包含C API,不应直接包含在C ++代码中:

#ifndef NS_FOO_H_
#define NS_FOO_H_

// an incomplete structure type substitutes for NS::C in C contexts
#ifndef __cplusplus
typedef struct NS_C NS_C;
#endif

NS_C *NS_C_new(void);
void NS_C_hello(NS_C *c);

#endif

文件 foo.hpp 包含实际的C ++ API,并负责将 foo.h 包含在C ++文件中:

#ifndef NS_FOO_HPP_
#define NS_FOO_HPP_

namespace NS {
    class C {
    public:
        C();
        void hello();
    };
}

// use the real declaration instead of the substitute
typedef NS::C NS_C;
extern "C" {
#include "foo.h"
}

#endif

实现文件 foo.cpp 是用C ++编写的,因此包含了 foo.hpp ,它还引入了 foo.h :< / p>

#include "foo.hpp"
#include <cstdio>

using namespace NS;

C::C() {}

void C::hello() {
    std::puts("hello world");
}

C *NS_C_new() {
    return new C();
}

void NS_C_hello(C *c) {
    c->hello();
}

如果您不想使C API可用于C ++代码,您可以将相关部分从 foo.hpp 移动到 foo.cpp

作为使用C API的示例,基本文件是 main.c

#include "foo.h"

int main(void)
{
    NS_C *c = NS_C_new();
    NS_C_hello(c);
    return 0;
}

此示例已使用以下编译器标志在gcc 4.6.1的MinGW版本中进行了测试:

g++ -std=c++98 -pedantic -Wall -Wextra -c foo.cpp
gcc -std=c99 -pedantic -Wall -Wextra -c main.c
g++ -o hello foo.o main.o

代码假定类型NS::C *struct NS_C *具有兼容的表示和对齐要求,几乎无处不在,但据我所知,C ++标准无法保证(感觉如果我错了,可以随意纠正我。

从C语言的角度来看,代码实际上调用了未定义的行为,因为您在技术上通过不兼容类型的表达式调用函数,但这是没有包装器结构和指针强制转换的互操作性的代价:

由于C不知道如何处理C ++类指针,因此可移植的解决方案是使用void *,您应该将其包装在一个结构中以获得某种级别的类型安全性:

typedef struct { void *ref; } NS_C_Handle;

这将在具有统一指针表示的平台上添加不必要的样板:

NS_C_Handle NS_C_new() {
    NS_C_Handle handle = { new C() };
    return handle;
}

void NS_C_hello(NS_C_Handle handle) {
    C *c = static_cast<C *>(handle.ref);
    c->hello();
}

另一方面,它会摆脱 foo.h 中的#ifndef __cplusplus,所以它实际上并没有那么糟糕,如果你关心可伸缩性,我会说去吧为了它。

答案 1 :(得分:1)

我不完全明白你要做什么,但这可能会有所帮助:

如果您希望C仍然可以访问它,请执行以下操作:

void foo();

namespace ns {
  using ::foo;
}

或使用宏:

#ifdef __cplusplus
#define NS_START(n) namespace n {
#define NS_END }
#else
#define NS_START(n)
#define NS_END
#endif

NS_START(ns)
void foo();
NS_END

答案 2 :(得分:0)

你的标题都搞砸了。

你可能想要更像的东西:

struct C;

#ifdef __cplusplus
extern "C" {
#else
typedef struct C C;
#endif

/* ... */

#ifdef __cplusplus
}
#endif