How not to pollute the global namespace with declarations of a C header?
Asked Answered
R

3

9

I'm trying to wrap a C library in C++, to make it a modern, high level and idiomatic C++ library. What I want to do, is to make the C objects completely opaque and/or directly unavailable from the C++ code and wrap/replace them with higher-level alternatives.

The problem I'm facing with is simple: I want to include the C header only to the C++ source, so that the C++ header when included won't include the C header's declarations as well, that is, it won't pollute the global namespace.

But it looks like the correct separation of the header and source files does not allow me to do that. Here is a very much dummified version of my problem, the comments will tell you the rest:


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));
    }
}

So my questions are: am I missing something very obvious here? Is this even possible to achieve? Is there a trick that I'm not aware of?

Rooke answered 21/10, 2015 at 15:32 Comment(22)
namespace m00 {#include "myheader.h"} (yes, this is partially sarcastic)Daven
What about using the pimpl idiom?Champion
You are trying to reuse types from your c library in the interface of your cpp library. As long as that is the case, you obviously cannot hide those types.Acherman
@CássioRenan thanks, I will take a look at that! (I've already read some hints about it, as my search returned some result about pimpl)Rooke
@AnalPhabet: That won't work, if the c-library isn't header only, because of the different name mangling (or lack thereof)Acherman
@Acherman so basically it is not possible, to only use the C part inside the source and not in the headers? (like (stupid ideas here they go =>) forward declaring the enum constants, but define their values in the source; or forward declare a class and define its inheritance in the source)Rooke
@Acherman surprisingly it does work.Muniz
@CássioRenan the pimpl mechanism creates runtime overhead, which I want to avoid if possible. My first solution was very similar, before I even knew this technique called pimpl, but then I found out, that a class can derive from a struct.. (also not solving the enum problem)Rooke
for the enums at least, you could declare an enum that represented every type in the C structs, in the .hpp file like so: struct Consts{enum values{/*values mirroring C consts struct here*/}}; which would allow you to convert from and to the underlying type, then write a specialized cast to convert between the two enums in your .cpp file.Coucal
Pimpl is a terrible idea. Kills a lot of optimizations.Botch
@Coucal umm.. I don't think that would be a great idea (convince me, if I'm wrong), as we have modern language support for enum classes which I truly want to use :) Not to mention, the lightness of an enum compared to a struct..Rooke
@Botch I agree, but if the concern is hiding names from the header, there's not a lot of options. Not that I know of, at least.Champion
@n.m.: That is indeed interesting - with what library and compiler have you tested it?Acherman
@PeterVaro: Of course you can, but any type, you want to use in your interface (directly or indirectly) has to be defined in your hpp-file.Acherman
@Acherman gcc, but it should work with any normal compiler. extern "C" names are usually not mangled.Muniz
@n.m. even if this is true (which would be awesome) -- the C values would be still available, because that namespace would be included by the .hpp, so in some ways, it answers my question, that those values won't pollute the global namespace, but on the other hand they will not hide/restrict access to them, right?Rooke
@n.m.: even if they are sourrounded by namespace ...? I thought the namespace is part of the name mangling. Lets say, I include two C-header files which both define the name foo and souround them with two different namespaces. How does the linker resolve a call to namespace1::foo then?Acherman
This just puts the included names in a namespace. To totally hide them you would need to do if you wanted to hide names exported by a C++ library. Write forwarders and implement pimpls. There's no need, namespaces solve the problem.Muniz
@Acherman yes, even when included in a namespace. The point of extern "C" is C compatibility. C doesn't mangle. If you include two such things, you will have an error. The linker would see both names as just foo and will not be able to resolve them correctly.Muniz
@Anal Phabet: Sorry, I was totally wrong please go ahead and post your suggestion as an answerAcherman
@n.m.: You are right of course, there is even an example in the standard about this. Don't know, what I was thinking.Acherman
@PeterVaro I was misremembering that with a scoped enum, you couldn't get the underlying value, period. (An aside: the only difference in cost I can find between a scoped enum and a struct containing just an unscoped enum definition is the type safety aspect--what I'm assuming is your concern, and that variables of the struct's type will typically be a byte, excepting its inclusion in another class as a base class, in which case the Base Class Optimization comes out to play, but you'd only use instantiations of the enum...)Coucal
R
3

In the comments of the question @AnalPhabet suggested sarcastically, that one should use #include of a C header inside a namespace. @n.m. confirmed, that it is actually a working solution, and now I tested it on my own setup, and fortunately it is working pretty fine.

(Although I have no idea, if this is implementation specific or not, but I tested on both g++ and clang++ and it is working.)

It does not solve the opaqueness problem, but at least it makes a bit harder to access to the raw C data directly as it is living in a separate namespace now, therefore the user can't accidentaly access, but willingly.

So, the my_header.hpp should look like this:

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);
    };
}

So wherever my_header.hpp is #include'd, the user can only access to the C values as follows:

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(..)
Rooke answered 21/10, 2015 at 22:3 Comment(3)
Thank you guys: @AnalPhabet and @n.m.!Rooke
@AnalPhabet how's so?Rooke
Types, verbibols, funcitons defined in the namespace. Is size_t std::size_t, ::size_t or mynaemspaec::size_t?Daven
S
2

If the whole idea of writing high-level and idiomatic C++ wrapper is to bring safety, automatic memory management and convenient C++ types like std::sting, I would include C header into cpp file only.

Provide clean idiomatic C++ interface, and use C library only in the implementation.

Do not afraid to write a couple of utility functions that convert C data to C++ and back. If a C++ class should hold C-specific data, and it is not possible to replace it with C++ analog, use some type erasure technique to keep clean interface.

I wouldn't worry about performance due to such wrapping until I see it on top in a profiler log. In most cases it is not a bottleneck.

Again, splitting interface and implementation is usually a win.

UPDATE

Initially, I was thinking more about project specific C++ interface rather than universal C++ wrapper around C library.

Solution with extern "C" wrapped into a namespace looks correct to me (see §7.5 of C++11 standard). But, I've never seen this technique in the wild.

You can go further and add nested detail namespace to not pollute my namespace with C types. This trick is popular in header only libraries:

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);
    };
}

Take into account that you can't make C functions completely opaque or wrap them to a single namespace when you link with static library. They have external linkage and know nothing about namespaces.

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);

Basically, all these declarations refer to the same C function. As C doesn't support namespaces and overloading, linker usually uses function names as unique identifiers (even argument types are ignored).

Anyway, this approach will help to avoid accidental access to C interface.

Swingle answered 21/10, 2015 at 18:14 Comment(8)
Thanks for the reply, although you just summerized what I'm trying to do here. But while I tried, I hit some walls, one of them is the above question, which unfortunately did not answer by you.Rooke
@PeterVaro hmm... if you don't use C structures and enums in your C++ interface, you won't need to include .h into .hpp file. What am I missing?Swingle
that's exactly what I want to achieve, and that's exactly what I asked: how not to include the original .h into the .hpp => but because both the enums and classes need some values from it, it looks like it is unavoidable.. cannot forward declare the wrapping types without their C counterparts..Rooke
@PeterVaro I would define C++ enum class in .hpp file and added function into .cpp that converts C++ enum to C enum and back. I wouldn't use C enum values in C++ enum definition. The same for C++ class vs C structure. I've added a link to my tiny C library with C++ wrapper.Swingle
so, you would push all the conversions to run-time, instead of translation time => which could and should be avoided in this case (not only because of the tiny performance overhead, but because of the unnecessary nature of it)Rooke
@PeterVaro I know, all C++ developers inclined to optimize performance of every single line of code (including myself). But in my experience, performance bottlenecks are usually in other places. Later, you will be able to make interface dirtier if it is need to improve performance. But, usually it is not. I maintain high performance software and most of performance problems aren't related to packing/copying and other things. Sure, this is all IMHO.Swingle
I tried to say, that I'm not talking about performance here, it is very likely that it is so little it is neglectable. I'm talking about the static checking and analysis the compiler (and I) can do. Not to mention the fact, that literally nothing justifies pushing this problem to the runtime side, when you are perfectly being able to do that on the translation time side.Rooke
Let us continue this discussion in chat.Swingle
D
0

I'm not sure if it's language legal, but I think extern "C" is just there to unmangle functions, so as long as you keep them in the .cpp file you can get away with this.

This is a little profane, but it seems to work with gcc 4.3.5. It demonstrates that you can use C functions while also hiding them in a namespace.

I didn't bother with inheriting struct_t, but it should probably work. I have no idea if you can pull off the 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;
}

Demo...

$ gcc foo.c -c -O3
$ g++ foo.o bar.cpp driver.cpp
$ ./a.out
Hello World 10!
Goodbye World 10 0.100000
$
Democracy answered 21/10, 2015 at 17:15 Comment(1)
At this stage of your answer, it does not solve anything => you are including bar.hpp into the user-code, which also includes foo.h, therefore all the C declarations will be accessible. I need a solution, where I only include the foo.h into bar.cpp and bar.hpp stays clean and not include any C related stuffs directly. The hard part is: both the enums and classes need something for their reasonable forward declarations..Rooke

© 2022 - 2024 — McMap. All rights reserved.