如何不使用 C 头文件的声明污染全局名称空间?
How not to pollute the global namespace with declarations of a C header?
我正在尝试用 C++ 包装一个 C 库,使其成为一个现代的、高级的和惯用的 C++ 库。我想做的是使 C 对象完全不透明 and/or 直接从 C++ 代码中不可用,并 wrap/replace 它们具有更高级别的替代方案。
我面临的问题很简单:我只想将 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));
}
}
所以我的问题是: 我是否遗漏了一些非常明显的东西?这甚至有可能实现吗?有什么我不知道的技巧吗?
我不确定它是否是合法的语言,但我认为 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
$
如果编写高级和惯用的 C++ 包装器的整个想法是带来安全、自动内存管理和方便的 C++ 类型,如 std::sting
,我将只将 C 头文件包含到 cpp
文件中.
提供干净的惯用 C++ 接口,并仅在实现中使用 C 库。
不要害怕编写一些将 C 数据转换为 C++ 并返回的实用函数。如果 C++ class 应该保存特定于 C 的数据,并且不可能用 C++ 模拟替换它,请使用 some type erasure technique 保持干净的界面。
我不会担心这种包装导致的性能问题,直到我在探查器日志中看到它位于顶部。在大多数情况下,这不是瓶颈。
同样,拆分接口和实现通常是双赢。
更新
最初,我考虑更多的是项目特定的 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);
};
}
请注意,当您 link 使用静态库时,您不能使 C 函数完全不透明或将它们包装到单个名称空间中。他们有外在的 link 年龄,对名称空间一无所知。
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 不支持名称空间和重载,linker 通常使用函数名称作为唯一标识符(甚至忽略参数类型)。
无论如何,这种方法将有助于避免意外访问 C 接口。
在问题的评论中 @AnalPhabet suggested sarcastically, that one should use #include
of a C header inside a namespace
. @n.m. 确认,它实际上是一个可行的解决方案,现在我在自己的设置上对其进行了测试,幸运的是它工作得很好。
(虽然我不知道这是否是特定于实现的,但我在 g++
和 clang++
上都进行了测试并且它正在工作。)
它没有解决不透明问题,但至少让更难直接访问原始C数据它现在住在一个单独的 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
是 #include
,用户只能按如下方式访问 C 值:
my::my_Consts_ALPHA // The wrapped value is => my::Consts::ALPHA
my::my_Type // The wrapped value is => my::Type
my::my_Type_method(t,..) // The wrapped value is => t.method(..)
我正在尝试用 C++ 包装一个 C 库,使其成为一个现代的、高级的和惯用的 C++ 库。我想做的是使 C 对象完全不透明 and/or 直接从 C++ 代码中不可用,并 wrap/replace 它们具有更高级别的替代方案。
我面临的问题很简单:我只想将 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));
}
}
所以我的问题是: 我是否遗漏了一些非常明显的东西?这甚至有可能实现吗?有什么我不知道的技巧吗?
我不确定它是否是合法的语言,但我认为 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
$
如果编写高级和惯用的 C++ 包装器的整个想法是带来安全、自动内存管理和方便的 C++ 类型,如 std::sting
,我将只将 C 头文件包含到 cpp
文件中.
提供干净的惯用 C++ 接口,并仅在实现中使用 C 库。
不要害怕编写一些将 C 数据转换为 C++ 并返回的实用函数。如果 C++ class 应该保存特定于 C 的数据,并且不可能用 C++ 模拟替换它,请使用 some type erasure technique 保持干净的界面。
我不会担心这种包装导致的性能问题,直到我在探查器日志中看到它位于顶部。在大多数情况下,这不是瓶颈。
同样,拆分接口和实现通常是双赢。
更新
最初,我考虑更多的是项目特定的 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);
};
}
请注意,当您 link 使用静态库时,您不能使 C 函数完全不透明或将它们包装到单个名称空间中。他们有外在的 link 年龄,对名称空间一无所知。
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 不支持名称空间和重载,linker 通常使用函数名称作为唯一标识符(甚至忽略参数类型)。
无论如何,这种方法将有助于避免意外访问 C 接口。
在问题的评论中 @AnalPhabet suggested sarcastically, that one should use #include
of a C header inside a namespace
. @n.m. 确认,它实际上是一个可行的解决方案,现在我在自己的设置上对其进行了测试,幸运的是它工作得很好。
(虽然我不知道这是否是特定于实现的,但我在 g++
和 clang++
上都进行了测试并且它正在工作。)
它没有解决不透明问题,但至少让更难直接访问原始C数据它现在住在一个单独的 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
是 #include
,用户只能按如下方式访问 C 值:
my::my_Consts_ALPHA // The wrapped value is => my::Consts::ALPHA
my::my_Type // The wrapped value is => my::Type
my::my_Type_method(t,..) // The wrapped value is => t.method(..)