我试图用C ++包装一个C库,使其成为一个现代的,高水平的,惯用的C ++库。我想要做的是使C对象完全不透明和/或直接从C ++代码中不可用,并用更高级别的替代品包装/替换它们。
我面临的问题很简单:我想只将C头包含在C ++源代码中,这样C ++头文件在包含时不会包含C头文件的声明为好吧,也就是说,它不会污染全局命名空间。
但看起来头文件和源文件的正确分离不允许我这样做。这是我的问题的一个非常模糊的版本,评论将告诉你其余的:
my_header.h:
typedef enum
{
my_Consts_ALPHA = /* some special value */,
my_Consts_BETA = /* other special value */,
} my_Consts;
typedef struct
{
// members...
} my_Type;
void
my_Type_method(my_Type *const,
my_Enum);
my_header.hpp:
namespace my
{
enum class Consts; // <-- This header is missing the constant values of
// this enum, because its values are defined by
// the C header :(
class Type : public my_Type // <-- The super struct is coming from the
// C header, but I don't want to include
// that header here :(
{
public:
void
method(Consts constant);
};
}
my_source.cpp:
extern "C"
{
#include "my_header.h"
}
#include "my_header.hpp"
namespace my
{
enum class Consts
{
ALPHA = my_Consts_ALPHA,
BETA = my_Consts_BETA,
};
void
Type::method(Consts constant)
{
my_Type_method(static_cast<my_Type *const>(this),
static_cast<my_Consts>(constant));
}
}
所以我的问题是:我错过了一些非常明显的东西吗?这有可能实现吗?有没有我不知道的伎俩?
答案 0 :(得分:3)
在讽刺提出的问题@AnalPhabet的评论中,应该在#include
内使用namespace
的C标头。 @n.m.证实,它实际上是一个有效的解决方案,现在我在自己的设置上进行了测试,幸运的是它工作正常。
(虽然我不知道,如果这是特定于实现的,但我在g++
和clang++
进行了测试,并且它正在运行。)
它没有解决 opaqueness 问题,但至少它使更难直接访问原始C数据,因为它生活在一个单独的{{ 1}}现在,用户不能随意访问,但心甘情愿。
因此,namespace
应如下所示:
my_header.hpp
因此,只要namespace my
{
extern "C"
{
#include "my_header.h"
}
enum class Consts
{
ALPHA = my_Consts_ALPHA,
BETA = my_Consts_BETA,
};
class Type : public my_Type
{
public:
void
method(Consts constant);
};
}
为my_header.hpp
,用户就只能按如下方式访问C值:
#include
答案 1 :(得分:2)
如果编写高级和惯用的C ++包装器的整个想法是带来安全性,自动内存管理和方便的C ++类型,如std::sting
,我只会将C头包含在cpp
文件中。 / p>
提供干净的惯用C ++接口,并仅在实现中使用C库。
不要害怕编写几个将C数据转换为C ++并返回的实用程序函数。如果C ++类应该包含特定于C的数据,并且无法用C ++模拟替换它,请使用some type erasure technique来保持干净的接口。
我不会担心由于这种包装而导致的性能,直到我在Profiler日志中看到它为止。在大多数情况下,它不是瓶颈。
同样,分割界面和实现通常都是胜利。
<强>更新强>
最初,我正在考虑更多关于项目特定的C ++接口,而不是围绕C库的通用C ++包装。
包含在命名空间中的extern "C"
的解决方案对我来说是正确的(参见C ++ 11标准的第7.5节)。但是,我从未在野外见过这种技术。
您可以进一步添加嵌套的detail
命名空间,以便不使用C类型污染my
命名空间。这个技巧在仅头文件库中很流行:
namespace my
{
namespace detail
{
extern "C"
{
#include "my_header.h"
}
}
enum class Consts
{
ALPHA = detail::my_Consts_ALPHA,
BETA = detail::my_Consts_BETA,
};
class Type : public detail::my_Type
{
public:
void
method(Consts constant);
};
}
考虑到当您与静态库链接时,不能使C函数完全不透明或将它们包装到单个命名空间。它们具有外部链接,对命名空间一无所知。
namespace A {
extern "C" void my_Type_method(my_Type *const, my_Enum);
}
namespace B {
extern "C" void my_Type_method(my_Type *const, my_Enum);
}
extern "C" void my_Type_method(my_Type *const, my_Enum);
基本上,所有这些声明都引用相同的C函数。由于C不支持名称空间和重载,链接器通常使用函数名作为唯一标识符(甚至忽略参数类型)。
无论如何,这种方法有助于避免意外访问C接口。
答案 2 :(得分:0)
我不确定它的语言是否合法,但我认为extern "C"
只是用于解开函数,所以只要你将它们保存在.cpp文件中就可以逃脱有这个。
这有点亵渎,但似乎与gcc 4.3.5一起使用。它演示了您可以使用C函数同时将它们隐藏在命名空间中。
我没有继承struct_t,但它应该可行。我不知道你是否可以取消enum class
。
<强> foo.h中强>
#ifndef foo_H
#define foo_H
typedef enum {
ALPHA,
BETA
} enum_t;
typedef struct
{
int i;
} struct_t;
void printit(struct_t print_me);
#endif // foo_H
<强> foo.c的强>
#include <stdio.h>
#include "foo.h"
void printit (struct_t print_me)
{
printf ("Hello World %d!\n", print_me.i);
}
<强> bar.hpp 强>
#ifndef bar_HPP
#define bar_HPP
namespace _foo {
// Don't need extern "C" since we're not using functions
#include "foo.h"
}
struct based_on_struct_t // : public _foo:struct_t // Do you really have to derive? It might be possible, but it's ugly
{
_foo::struct_t i;
double j;
based_on_struct_t (int _i, double _j) : j(_j) { i.i = _i; }
void print(void); // Gonna call printit, MUST be in .cpp
};
#endif // bar_HPP
<强> bar.cpp 强>
namespace _foo{
extern "C" {
#include "foo.h"
}
}
#include "bar.hpp"
#include <stdio.h>
void based_on_struct_t::print (void) {
// Call the old version...
printit(i);
// And do new crap
printf ("Goodbye World %d %f\n", i.i, j);
}
<强> driver.cpp 强>
#include "bar.hpp"
int main (void) {
based_on_struct_t B(10, .1);
B.print();
return 0;
}
...演示
$ gcc foo.c -c -O3
$ g++ foo.o bar.cpp driver.cpp
$ ./a.out
Hello World 10!
Goodbye World 10 0.100000
$