2015-10-21 5 views
8

Ich versuche, eine C-Bibliothek in C++ zu wickeln, um es zu einer modernen, hochwertigen und idiomatischen C++ - Bibliothek zu machen. Was ich tun möchte, ist, die C-Objekte vollständig undurchsichtig und/oder direkt aus dem C++ - Code nicht verfügbar zu machen und sie durch höhere Alternativen zu ersetzen.Wie verschmutzt man den globalen Namensraum nicht mit Deklarationen eines C-Headers?

Das Problem, mit dem ich konfrontiert bin, ist einfach: Ich möchte den C-Header nur in die C++ - Quelle, so dass die C++ - Header enthalten auch die C-Header-Deklarationen nicht enthalten, das heißt, es gewonnen hat Verschmutze den globalen Namensraum nicht.

Aber es sieht aus wie die richtige Trennung der Header-und Quelldateien erlaubt mir nicht, dies zu tun. Hier ist eine sehr viel dummified Version meines Problems, werden die Kommentare sagen Sie den 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)); 
    } 
} 

Also meine Fragen sind: bin ich etwas sehr offensichtlich hier fehlt? Ist das überhaupt möglich? Gibt es einen Trick, den ich nicht kenne?

+0

' Namespace m00 { #include "myheader.h"} '(ja, das ist teilweise sarkastisch) –

+0

Wie wäre es mit dem [pimpl idiom] (https://en.wikipedia.org/wiki/Opaque_pointer)? –

+0

Sie versuchen, Typen aus Ihrer c-Bibliothek in der Schnittstelle Ihrer cpp-Bibliothek wiederzuverwenden. Solange das der Fall ist, können Sie diese Typen natürlich nicht verbergen. – MikeMB

Antwort

4

In den Kommentaren der Frage @AnalPhabet schlug sarkastisch vor, dass man #include eines C-Headers innerhalb einer namespace verwenden sollte. @n.m. bestätigt, dass es tatsächlich eine funktionierende Lösung ist, und jetzt habe ich es auf meinem eigenen Setup getestet, und glücklicherweise funktioniert es ziemlich gut.

(Obwohl ich keine Ahnung habe, ob diese Implementierung spezifische oder nicht, aber ich getestet sowohl auf g++ und clang++ und es funktioniert.)

Es löst nicht das Undurchsichtigkeit Problem, aber zumindest macht es ein bisschen härter Zugriff auf die rohen C-Daten direkt, wie es in einem separaten namespace jetzt lebt, daher kann der Benutzer nicht versehentlich zugreifen, aber bereitwillig.

So sollte die my_header.hpp wie folgt aussehen:

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

Egal, wo my_header.hpp#include ‚d ist, kann der Benutzer nur Zugriff auf die C-Werte wie folgt:

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(..) 
+0

Danke Leute: @AnalPhabet und @ n.m.! –

+0

@AnalPhabet wie geht's? –

+0

Typen, Verbibole, Funktionen, die im Namensraum definiert sind. Ist 'size_t'' std :: size_t', ':: size_t' oder' mynaaspaec :: size_t'? –

0

Ich bin mir nicht sicher, ob es Sprache legal ist, aber ich denke extern "C" ist nur da, um Funktionen zu entmangeln, so lange Sie sie in der .cpp-Datei behalten, können Sie damit durchkommen.

Dies ist ein wenig profane, aber es scheint mit gcc 4.3.5 zu arbeiten. Es zeigt, dass Sie C-Funktionen verwenden und sie auch in einem Namespace ausblenden können.

Ich kümmerte mich nicht um erben von struct_t, aber es sollte wahrscheinlich funktionieren. Ich habe keine Ahnung, ob Sie die enum class abziehen können.

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 
$ 
+0

In diesem Stadium Ihrer Antwort löst es nichts => Sie "bar.hpp" in den Benutzer-Code, die auch "foo.h", also alle enthalten C-Erklärungen sind zugänglich. Ich brauche eine Lösung, bei der ich nur 'foo.h' in' bar.cpp' einfüge und 'bar.hpp' sauber bleibt und keine C-bezogenen Inhalte direkt enthält. Der schwierige Teil ist: Sowohl die Enums als auch die Klassen brauchen etwas für ihre vernünftige Vorwärtsdeklaration. –

2

Wenn die ganze Idee auf hohe Ebene und idiomatischen C++ Wrapper des Schreibens ist Sicherheit zu bringen, automatische Speicherverwaltung und bequeme C++ Typen wie std::sting, würde ich nur C-Header in cpp Datei enthalten.

Stellen Sie eine saubere idiomatische C++ - Schnittstelle bereit, und verwenden Sie die C-Bibliothek nur in der Implementierung.

Haben Sie keine Angst, ein paar Dienstprogrammfunktionen zu schreiben, die C-Daten in C++ und zurück konvertieren. Wenn eine C++ - Klasse C-spezifische Daten enthalten soll und es nicht möglich ist, sie durch C++ - Analog zu ersetzen, verwenden Sie some type erasure technique, um die Schnittstelle sauber zu halten.

Ich würde mir keine Sorgen über die Leistung aufgrund solcher Verpackung machen, bis ich es oben in einem Profiler-Protokoll sehe. In den meisten Fällen ist es kein Flaschenhals.

Auch hier ist die Trennung von Interface und Implementierung normalerweise ein Gewinn.

UPDATE

Anfangs wurde ich mehr über projektspezifische C++ Schnittstelle denken als universelle C++ Wrapper um Bibliothek C.

Lösung mit extern "C" in einen Namespace verpackt sieht für mich richtig aus (siehe §7.5 der C++ 11-Norm). Aber ich habe diese Technik noch nie in freier Wildbahn gesehen.

Sie können weiter gehen und verschachtelten detail Namespace hinzufügen, um my Namespace mit C-Typen nicht zu verschmutzen. Dieser Trick ist sehr beliebt in Bibliotheken Header nur:

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

berücksichtigen, dass Sie nicht C-Funktionen völlig undurchsichtig oder wickeln Sie sie zu einem einzigen Namespace, wenn Sie einen Link mit statischer Bibliothek machen. Sie haben externe Verknüpfungen und wissen nichts über Namensräume.

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

Grundsätzlich beziehen sich alle diese Deklarationen auf die gleiche C-Funktion. Da C Namespaces und Überladung nicht unterstützt, verwendet Linker normalerweise Funktionsnamen als eindeutige Bezeichner (selbst Argumenttypen werden ignoriert).

Wie auch immer, dieser Ansatz hilft, einen versehentlichen Zugriff auf die C-Schnittstelle zu vermeiden.

+0

Danke für die Antwort, obwohl Sie gerade zusammengefasst haben, was ich hier zu tun versuche. Aber während ich es versuchte, traf ich einige Wände, eine davon ist die obige Frage, die leider nicht von dir beantwortet wurde. –

+0

@PeterVaro hmm ... Wenn Sie in Ihrer C++ - Oberfläche keine C-Strukturen und Enums verwenden, brauchen Sie '.h' nicht in' .hpp' einzufügen. Was vermisse ich? – Stas

+0

das ist genau das, was ich erreichen möchte, und genau das habe ich gefragt: wie man das Original '.h' nicht in' .hpp' einbindet => aber da sowohl die enums als auch die Klassen einige Werte benötigen, sieht es so aus es ist unvermeidbar .. kann nicht die Wrapping-Typen ohne ihre C Gegenstücke deklarieren. –

Verwandte Themen