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

Gri*_*ngo 5 c c++ namespaces

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

对于每个C ++实现,除了C ++头文件之外,我们通常还会提供一个C头文件,以使功能也可用于.c文件。

因此,我们的大多数文件如下所示:

foo.hpp:

class C { 
    int foo();
};
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

foo.cpp:

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

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

extern "C"
C*  utility_function ( C* ) { /* implementation here...*/ }
Run Code Online (Sandbox Code Playgroud)

题:

假设我想像这样向类添加名称空间:

foo.hpp:

namespace NS {
    class C { 
        int foo();
    };
}
Run Code Online (Sandbox Code Playgroud)

C头中遵循的最佳方案是什么?

我考虑了一些选择,但我正在寻找最优雅,安全和易于阅读的内容。有没有标准的使用方式?


这是我考虑过的选项:(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* );
Run Code Online (Sandbox Code Playgroud)

这给头增加了一些复杂性,但是使实现保持不变。


  • 选项2:使用C结构包装名称空间:

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

foo.h

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

int foo( NS_C* );
NS_C*  utility_function( NS_C* );
Run Code Online (Sandbox Code Playgroud)

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 );
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

Chr*_*oph 3

我发现以某种方式分解代码更容易,这样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
Run Code Online (Sandbox Code Playgroud)

文件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
Run Code Online (Sandbox Code Playgroud)

实现文件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();
}
Run Code Online (Sandbox Code Playgroud)

如果您不想让 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;
}
Run Code Online (Sandbox Code Playgroud)

此示例已使用以下编译器标志通过 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
Run Code Online (Sandbox Code Playgroud)

代码假设类型NS::C *struct NS_C *具有兼容的表示和对齐要求,这几乎在任何地方都是如此,但据我所知,C++ 标准并不能保证(如果我在这里错了,请随时纠正我)。

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

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

typedef struct { void *ref; } NS_C_Handle;
Run Code Online (Sandbox Code Playgroud)

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

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();
}
Run Code Online (Sandbox Code Playgroud)

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