为带有C头文件的c++实现添加名称空间

Adding namespaces to C++ implementations that have a C header

本文关键字:添加 实现 空间 c++ 文件      更新时间:2023-10-16

我们有一个C和c++代码的大项目。

对于每个c++实现,除了c++头文件,我们通常还提供了一个C头文件来允许。C文件的功能可用。

所以,我们的大多数文件看起来像这样:

foo.hpp:

class C { 
    int foo();
};

foo:

#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头文件中遵循的最佳方案是什么?

我考虑了几个选项,但我想要最优雅,安全,易于阅读。有标准的方法吗?


以下是我考虑过的选项:(为了简单起见,我使用了extern "C"结构)
  • 选项1:通过在每个头文件中添加一些代码来欺骗编译器:

foo。

#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结构体包装命名空间:

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

foo。

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

这些是唯一的方案吗?这些方法是否有隐藏的缺点?

我发现以某种方式分解代码更容易,这样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:

#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++代码可以使用C API,你可以把相关的部分从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;
}

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

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,所以它实际上并没有那么糟糕,如果你关心可移植性,我建议使用它。

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

如果你想让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

你的头乱糟糟的

你可能想要更像:

struct C;
#ifdef __cplusplus
extern "C" {
#else
typedef struct C C;
#endif
/* ... */
#ifdef __cplusplus
}
#endif