2016-08-18 2 views
7

Ich entwickle eine Bibliothek. Ich habe Interface-Klasse, die von außen aufgerufen werden würde.Verbergen der Implementierungsdetails durch Reduzieren der Anzahl der aufgefüllten Header

Ich habe auch einen internen Motor, der nicht von außen aufgerufen werden sollte.

Während ich hier und da lese, sollte ich die interne Engine-Klasse verstecken und nicht einmal ihren Header auffüllen. Da habe ich die folgende Struktur:

interface.hpp:

#include "engine.hpp" 
class interface{ 
private: 
    enigne eng; 
}; 

interface.cpp:

#include "engine.hpp" 
//code that uses member variables and functions from eninge 

engine.hpp:

class engine{}; 

Um das Problem zu bevölkern "engine.hpp" zu lösen, sollte ich den Code ändern:

interface.hpp:

class engine; 
class interface{ 
private: 
    some_smart_pointer<enigne> eng_ptr; 
}; 

interface.cpp:

#include "engine.hpp" 
//code that uses member variables and functions from eninge 

enigne.hpp:

class engine{}; 

Dies löste das Problem. Ab jetzt wird engine dynamisch zugewiesen. Alle seine Mitgliedsvariablen befinden sich im freien Speicher.

Ich kann nicht verstehen, dass ich mein Design ändern und Engine auf dem freien Speicher zuweisen muss, um das Problem des Verbergens der Implementierungsdetails zu lösen. Gibt es eine bessere Lösung?

P.S. Ich frage nicht, warum diese Lösung funktioniert. Ich weiß, dass es darum geht, die Größe der Triebwerksklasse zu kennen, wenn ich sie im Stapel lassen würde. Meine Frage besteht darin, nach einem anderen Design zu fragen, das das Problem lösen könnte.

EDIT:

Beide interface und engine haben Membervariablen.

+2

Jede angebotene Lösung wird Ihre Implementierung nur erschweren. Wenn Ihnen die Kosten für die Verwendung der Standard-Allokatoren Sorgen bereiten (Metadatenverschwendung und/oder -geschwindigkeit), [können Sie immer Ihren eigenen Allokator schreiben] (http://www.gotw.ca/gotw/028.htm) – StoryTeller

+2

If ' Interface' hat keine Member und nur virtuelle Funktionen, Sie können 'Engine' komplett ausblenden. – Jarod42

+0

Es gibt immer eine Hacky-Lösung: Verwenden Sie eine einzelne private 'char Daten []' und 'static_cast (Daten)'. Aber das ist ein Wartungs-Albtraum und * wirklich * hacky. Stattdessen wird das Schreiben eines eigenen Zuordners wahrscheinlich fast genauso effizient, aber viel sauberer sein. Natürlich * messen * Sie zuerst, wenn PIMPL wirklich ein Performance-Engpass ist, bevor Sie vorzeitige Optimierungen vornehmen. –

Antwort

2

Dies ist die Standardlösung für dieses Problem: Es heißt das PIMPL (Zeiger auf Implementierung) Idiom. Und wie der Name schon sagt, geht es immer einen Zeiger (oder Smart-Zeiger) auf die Implementierungsklasse, weil C++ einfach dies nicht zulässt:

class engine; 
class interface{ 
private: 
    enigne eng; 
}; 
+1

Dies beantwortet die Frage nicht und ignoriert das PostScript des OP vollständig. – StoryTeller

+0

@StoryTeller Nein, tut es nicht. Das OP bat um ein Standarddesign, und pimpl ist in der Tat die richtige Antwort. –

+0

@StoryTeller: Wenn es eine bessere Lösung gäbe, wäre PIMPL wahrscheinlich nicht mehr der Standard. Es ist eine negative Antwort, aber so ist es. –

4

Sie verwenden die PIMPL Idiom. Die andere Möglichkeit, die Implementierung zu verbergen, besteht in der Verwendung von Schnittstellen, d. H. Einer abstrakten Basisklasse und einer Factory-Funktion:

Schnittstelle.HPP:

class interface{ 
public: 
    virtual ~interface(){} 
    virtual void some_method() = 0; 
}; 

// the factory function 
static some_smart_pointer<interface> create(); 

interface.cpp:

#include "interface.hpp" 
#include "engine.hpp" 

class concrete : public interface{ 
public: 
    virtual void some_method() override { /* do something with engine */ } 
private: 
    engine eng; 
}; 

some_smart_pointer<interface> create(){ return new concrete; } 

main.cpp

#include "interface.hpp" 

int main() 
{ 
    auto interface = create(); 
    interface->some_method(); 

    return 0; 
} 

Der Nachteil hierbei ist, dass Sie dynamisch interface statt engine zuweisen muss.

Weitere Diskussion über PIMPL und Schnittstellen here und here

EDIT:

Basierend auf STL-Containern und Howard Hinnant der stack allocator, einen dritten Weg Variablen in den freien Speicher können zu vermeiden sein:

interface.hpp:

class interface{ 
public: 
    interface(); 
    ~interface(); 

    // I disable copy here, but you can implement them 
    interface(const interface&) = delete; 
    interface& operator=(interface&) = delete; 

private: 
    engine* eng; 
}; 

interface.cpp:

#include "interface.hpp" 
#include "engine.hpp" 
#include "short_alloc.h" 

#include <map> 

namespace 
{ 
    std::map< 
     interface*, 
     engine, 
     std::default_order<interface*>, 
     // use a stack allocator of 200 bytes 
     short_alloc<std::pair<interface*, engine>, 200> 
    > engines; 
} 

interface::interface(): 
    eng(&engines[this]) 
     // operator[] implicitly creates an instance and returns a reference to it 
     // the pointer gets the address 
{ 
} 

interface::~interface() 
{ 
    // destroy the instance 
    engines.erase(this); 
} 
+0

Danke .. da ist etwas was ich nicht verstehen konnte. Ist in diesem Fall nicht auch Engine auf dem kostenlosen Store zugeordnet? –

+0

@HumamHelfawi Ich habe meine Antwort mit einer experimentellen Version ohne dynamische Zuweisung bearbeitet. Ich weiß nicht wirklich, was ich darüber denken soll, sag mir deine Meinung – wasthishelpful

+0

Umm, wie benutzt 'std :: map' den kostenlosen Speicher? – StoryTeller

3

Ich kann nicht verstehen, dass ich meinen Entwurf ändern und Motor zur Lösung des Problems zu verstecken Implementierungsdetails auf dem freien Speicher zuteilen.

Sie scheinen bereits zu haben, es selbst erklärt:

Ich weiß, es geht um die Größe der Motorklasse zu wissen

der Tat. Wenn der interface ein Mitglied von engine Typ hätte, dann müsste es unbedingt die Größe dieses Mitglieds wissen - andernfalls könnte die Größe interface selbst nicht bekannt sein. Die Größe eines unvollständigen Typs kann nicht bekannt sein. Das Definieren des Member-Typs würde das lösen, widerspricht jedoch Ihrem Wunsch, die Implementierung zu verbergen.

Gibt es eine bessere Lösung?

Es gibt keine bessere Lösung. PIMPL mit Free Store - das ist das Muster, das Sie derzeit verwenden - ist so gut wie es geht.

Technisch erfordert PIMPL nicht, dass Sie den kostenlosen Speicher verwenden. Sie können das Objekt an beliebiger Stelle speichern. Sie könnten statischen Speicher verwenden. Aber das würde die Anzahl der Instanzen begrenzen, die Sie haben können. Sie können sogar einen Speicherpuffer als Mitglied von interface zuweisen, aber Sie müssen die Größe eines solchen Puffers fest codieren und er muss mit der Größe (und Ausrichtung) von engine übereinstimmen.

Ich würde diese beiden theoretischen Vorschläge als kludges kategorisieren - letzteres insbesondere.Statischer Speicher kann sich lohnen, wenn Ihr Profiling darauf hinweist, dass der Overhead dieser speziellen zusätzlichen Zuweisung signifikant ist und der Verzicht auf PIMPL keine Option ist.

1

Die typische Lösung verstecken Implementierungen und keine Objekte zwingen, auf dem Heap zugewiesen werden soll sich in einem detail Namespace setzen, und setzt die Header für diese Typen in einem Detail-Unterverzeichnis.

Beachten Sie, dass einige Aspekte Ihrer Implementierung offengelegt werden, da sie sich auf die ABI Ihrer Bibliothek auswirken. Es gibt keine Lösung, die Ihnen sowohl ABI-Isolierung und Vermeidung von Zuweisen von Objekten auf dem Heap geben kann.

Wahrscheinlich sind Sie am besten mit pimpl, wie Sie beschreiben.

Verwandte Themen