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?

Peter Varo
  • 11,726
  • 7
  • 55
  • 77
  • `namespace m00 {#include "myheader.h"}` (yes, this is partially sarcastic) – набиячлэвэли Oct 21 '15 at 15:42
  • What about using [the pimpl idiom](https://en.wikipedia.org/wiki/Opaque_pointer)? – Not a real meerkat Oct 21 '15 at 15:42
  • 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. – MikeMB Oct 21 '15 at 15:43
  • @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) – Peter Varo Oct 21 '15 at 15:44
  • @AnalPhabet: That won't work, if the c-library isn't header only, because of the different name mangling (or lack thereof) – MikeMB Oct 21 '15 at 15:45
  • @MikeMB 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) – Peter Varo Oct 21 '15 at 15:47
  • @MikeMB surprisingly it does work. – n. m. could be an AI Oct 21 '15 at 15:52
  • @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) – Peter Varo Oct 21 '15 at 15:52
  • 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. – jaggedSpire Oct 21 '15 at 15:59
  • 2
    Pimpl is a terrible idea. Kills a lot of optimizations. – SergeyA Oct 21 '15 at 16:00
  • @jaggedSpire 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 class`es which I truly want to use :) Not to mention, the lightness of an enum compared to a struct.. – Peter Varo Oct 21 '15 at 16:03
  • @SergeyA 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. – Not a real meerkat Oct 21 '15 at 16:08
  • @n.m.: That is indeed interesting - with what library and compiler have you tested it? – MikeMB Oct 21 '15 at 16:15
  • @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. – MikeMB Oct 21 '15 at 16:18
  • @MikeMB gcc, but it should work with any normal compiler. `extern "C"` names are usually not mangled. – n. m. could be an AI Oct 21 '15 at 16:22
  • @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? – Peter Varo Oct 21 '15 at 16:26
  • @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? – MikeMB Oct 21 '15 at 16:30
  • 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. – n. m. could be an AI Oct 21 '15 at 16:37
  • @MikeMB 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. – n. m. could be an AI Oct 21 '15 at 16:42
  • @Anal Phabet: Sorry, I was totally wrong please go ahead and post your suggestion as an answer – MikeMB Oct 21 '15 at 16:43
  • @n.m.: You are right of course, there is even an example in the standard about this. Don't know, what I was thinking. – MikeMB Oct 21 '15 at 16:44
  • 1
    @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](http://stackoverflow.com/questions/25786853/), but you'd only use instantiations of the enum...) – jaggedSpire Oct 21 '15 at 16:48

3 Answers3

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(..)
Community
  • 1
  • 1
Peter Varo
  • 11,726
  • 7
  • 55
  • 77
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.

Stas
  • 11,571
  • 9
  • 40
  • 58
  • 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. – Peter Varo Oct 21 '15 at 18:22
  • @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? – Stas Oct 21 '15 at 18:27
  • 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.. – Peter Varo Oct 21 '15 at 18:30
  • @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. – Stas Oct 21 '15 at 18:34
  • 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) – Peter Varo Oct 21 '15 at 18:36
  • @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. – Stas Oct 21 '15 at 18:43
  • 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. – Peter Varo Oct 21 '15 at 18:49
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/93005/discussion-between-stas-and-peter-varo). – Stas Oct 21 '15 at 18:53
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
$
QuestionC
  • 10,006
  • 4
  • 26
  • 44
  • 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.. – Peter Varo Oct 21 '15 at 18:45