【发布时间】:2011-12-28 07:04:15
【问题描述】:
我们有一个包含 C 和 C++ 代码的大型项目。
对于每个 C++ 实现,除了 C++ 头文件外,我们通常还提供一个 C 头文件以允许 .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 标头中遵循的最佳方案是什么?
我已经考虑了几个选项,但我正在寻找最优雅、安全且易于阅读的选项。有没有标准的使用方式?
以下是我考虑过的选项:
(为简单起见,我省略了 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 );
...
}
这些是唯一的方案吗?这些中是否有任何隐藏的缺点?
【问题讨论】:
-
这很模糊。
C的奇怪 ifdef 声明闻起来像是几英里外未定义的行为;但更重要的是,我看不到重点:C 程序将用foo()做什么?它周围没有任何有用的C-pointer,所以那里发生了什么? -
最初
class C在 .c 文件中实现为struct C。 .h 文件始终包含foo(C*)函数。 foo.h 的客户从不知道struct C的内容。它是“C”术语的封装。在某些时候,struct C需要发展,我们将其更改为class C并将其实现移至 .cpp 文件中。从来没有 C 程序对C对象做任何事情。它总是引用C-pointer。这对于超过 10 年的代码来说是典型的。我希望这能澄清我的意图。 -
在这种情况下,我只需让 C 函数接受
void*参数,并在 C++ 实现中执行强制转换:`inf foo(void * p) { return reinterpret_cast(p)->foo(); “从未使用过”的 C 结构类型简直令人困惑,并且使事情变得更糟。如果您想要一个永远不会取消引用的指针,请使用 void 指针。 -
虽然从标准的角度来看,您的提议在技术上是正确的,但我们广泛使用我所描述的技术,因为它提供了
void*无法提供的类型安全性。它也更具可读性(至少如果有人解释了这个成语)。我们已经使用了这两个成语,并且已经习惯了这个,因为我们发现我们的错误更少,而且读起来更好。
标签: c++ c namespaces