我们有一个包含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"为了简单起见,我省略了结构)
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)
这些是唯一的方案吗?这些中是否有任何隐藏的缺点?
我发现以某种方式分解代码更容易,这样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中的内容,所以它实际上并没有那么糟糕,如果你关心可盈利性,我会说去吧。