如何不使用C头的声明污染全局命名空间?

时间:2015-10-21 15:32:26

标签: c++ c++11 header namespaces wrapper

我试图用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));
    }
}

所以我的问题是:我错过了一些非常明显的东西吗?这有可能实现吗?有没有我不知道的伎俩?

3 个答案:

答案 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
$