我们有一个包含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"
构造)
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 );
...
}
这些是唯一的方案吗?这些中有任何隐藏的缺点吗?
答案 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