2013-07-09 3 views
7

Napisałem bibliotekę (nieważne, co ona robi), która oczywiście ma swój plik nagłówkowy. Teraz chcę ukryć prywatne elementy tego pliku nagłówkowego, więc jeśli udostępnię komuś moją bibliotekę, powinien on widzieć tylko członków publicznych (najlepiej bez definicji klasy, nic poza definicjami funkcji). Jednym ze sposobów byłoby stworzenie nagłówka w stylu C, który będzie zawierał pewną metodę "init", która będzie używana do tworzenia instancji rzeczywistej klasy biblioteki, a użytkownik będzie musiał przekazać wskaźnik tego obiektu do każdej funkcji do wykonuj pracę.Ukrywanie prywatnych członków biblioteki C++

Czy to dobra praktyka?

Czy istnieją inne publicznie akceptowane sposoby robienia czegoś takiego?

Z góry dziękuję.

+2

Wygląda na to, że można użyć idiomu Pimpl. Ale niezależnie od metody, której używasz na końcu, musisz mieć świadomość, że ukrywanie implementacji może mieć problemy z wydajnością (kompilator nie będzie w stanie wstawić wiele). – syam

Odpowiedz

11

Oprócz wzoru Factory (które, moim zdaniem, może stać się niewygodne), można również ukryć swoje prywatne członków tył w PIMPL (wskaźnik do realizacji):

// Interface.hpp 
class Implementation; 
class Interface { 
public: 
    Interface() : pimpl(new Implementation()) {} 
    void publicMethod(); 
private: 
    std::unique_ptr<Implementation> pimpl; 
}; 

// Interface.cpp 
class Implementation { 
public: 
    void PrivateMember(); 
}; 

void Interface::publicMethod() { pimpl->PrivateMember(); } 

ma ten zaletą ukrywania implementacji, kosztem pojedynczego wskaźnika pośredniego, niewiele różniącego się od typowego modelu Fabryki opartego na dziedziczeniu.

Może to być również stabilne ABI. Zmiany w implementacji nie wpłyną na powiązania, ponieważ żadne zmiany nie będą widoczne dla reszty programu. Jest to dobry wzór do zastosowania na przykład przy wdrażaniu obiektów wspólnych.

Jest to również popularny idiom C++, więc inni programiści C++ rozpoznają go bez pytania.

W przypadku klasy, które będą zgodne z wzorcem Singleton, można uniknąć narażania PIMPL w ogóle i po prostu napisać całą wdrożenie w anonimowej namespace w pliku .cpp, gdzie można umieścić jak najwięcej państwowych i prywatnych działa tak, jak sobie życzysz, nawet bez podpowiedzi w interfejsie.

+1

Do pytającego: to, przynajmniej moim zdaniem, lepsza odpowiedź niż moja. –

+0

@EricFinn Tak, naprawdę myślałem tak samo. Dzięki za odpowiedzi, chłopaki mi pomogliście :) – khajvah

1

Myślę, że potrzebujesz stworzyć Dynamic Link Library (dll).

Please take a quick look at this link:

+0

Dzięki za odpowiedź. Problem polega na tym, że jestem użytkownikiem Linuksa i nie sądzę, że istnieje sposób na stworzenie biblioteki DLL w Linuksie. Jakie są alternatywy? – khajvah

+1

Właściwie to nie mam pojęcia o Linuksie. ale znalazłem tę odpowiedź blisko twojej ... Mam nadzieję, że to pomoże! http://stackoverflow.com/questions/206272/hiding-private-data-members-c –

+0

Dzięki, popatrzę na te idiomy :) – khajvah

6

Można utworzyć publicznie widoczny interfejs. Utwórz klasę abstrakcyjną z funkcjami, które chcesz eksponować, a następnie dodaj ją do swojej implementacji.

Na przykład, interfejs:

class Interface { 
public: 
    virtual void publicMethod() = 0; 
... 
}; 

i wdrożenie:

class Implementation : Interface { 
public: 
    virtual void publicMethod(); 
private: 
    int hiddenMethod(); 
}; 

Wtedy tylko wyeksportować symbole Interface. Teraz, aby użytkownik biblioteki, aby uzyskać instancji interfejsu, które są faktycznie Wdrożenia, trzeba zapewnić fabryczne:

class Factory { 
public: 
    //can create and return an Implementation pointer, but caller will get an Interface pointer 
    std::shared_ptr<Interface> getImplementationInstance(); 
} 
+0

Ok, załóżmy, że jestem użytkownikiem tego interfejsu. Tak więc tworzę instancję klasy Interface, a następnie wywołuję funkcje ALE jak to będzie wiedzieć, że wołam methonds of Implementation class (jak stworzyliśmy tylko instancję Interface) – khajvah

+0

@khajvah Dobra rada. Zaktualizuję moją odpowiedź. –

+0

@khajvah, którego nie potrzebujesz znać. Interfejs powinien jednak zapewniać możliwość tworzenia różnych implementacji i zwracać inteligentny wskaźnik do abstrakcyjnego typu bazy. – juanchopanza

0

może warto spojrzeć na kopercie/list idiom, projektowania mostów wzór lub wzorzec proxy. Zasadniczo stworzysz zewnętrzną (publiczną) klasę, która po prostu przekieruje twoje publiczne wywołania metod do wewnętrznej (prywatnej) klasy. Twój nagłówek InnerClass.h musi tylko być widoczny/znany twoim plikom źródłowym OuterClass.cpp i InnerClass.cpp.

Każdy z tych wzorów zapewnia mechanizm oddzielania implementacji od interfejsu, aby wywołujący nie był połączony z implementacją. Czasami jest to pożądane, aby zmniejszyć zależności kompilatora na dużych projektach C++. Innym częstym powodem, dla którego chce się to zrobić, jest sytuacja, w której chcesz ukryć szczegóły implementacji, aby wywołujący widział tylko jeden nieprzezroczysty wskaźnik.

======= OuterClass.h ===== 
class InnerClass; // forward declaration is all that's needed 

class OuterClass { 
    private: 
     InnerClass *pInner; 

    public: 
     InnerClass(); 

     bool doSomething(); 
}; 

======= OuterClass.cpp ====== 
#include "OuterClass.h" 
#include "InnerClass.h" 

OuterClass::OuterClass() : 
    pInner(new InnerClass()) 
{ 
} 

bool OuterClass::doSomething() 
{ 
    return pInner->doSomething(); 
} 
+0

Czy możesz rozwinąć swoją odpowiedź? Twoja odpowiedź może być jeszcze bardziej pomocna, jeśli dodasz opisy tych wzorców, sposób ich użycia i uzasadnienie ich sugestii. – greyfade

2

Base Eric Finn's answer, można po prostu zadeklarować klasę interface do przechowywania wszystkich publicznych metod, które uważane są twoje API i ukryć wszystkie implementacje i prywatne członków/metod w klasie realizacji, która dziedziczy interface klasę, oto przykład:

Twój plik nagłówka: my_api.h

// your API in header file 
// my_api.h 
class interface { 
public: 
    static interface* CreateInstance(); 
    virtual void draw() = 0; 
    virtual void set(int) = 0; 
}; 

implementacja (wspólna biblioteka): my_api.cpp (użytkownicy nie będą widzieć tego, kiedy sprawiają, że wspólna biblioteka) Więc można ukryć wszystko implementacja i priva Te metody/członków tutaj

#include "my_api.h" 
     // implementation -> in .cc file 
class implementation : public interface { 
    int private_int_; 
    void ReportValue_(); 
public: 
    implementation(); 
    void draw(); 
    void set(int new_int); 
}; 

implementation::implementation() { 
    // your actual constructor goes here 
} 

void implementation::draw() { 
    cout << "Implementation class draws something" << endl; 
    ReportValue_(); 
} 

void implementation::ReportValue_() { 
    cout << "Private value is: " << private_int_ << endl; 
} 
void implementation::set(int new_int) { 
    private_int_ = new_int; 
} 
interface* interface::CreateInstance() { 
    return new implementation; 
} 

jaki użytkownik korzysta z API:

#include <iostream> 
#include "my_api.h" 

int main(int argc, const char * argv[]) 
{ 

    using namespace std; 
    interface* a; interface* b; 
    a = interface::CreateInstance(); 
    a->set(1); 
    b = interface::CreateInstance(); 
    b->set(2); 
    b->draw(); 
    a->draw(); 
    return 0; 
} 

wyjściowa:

Implementation class draws 
Private int is: 2 
Implementation class draws 
Private int is: 1  

W tym wzorze, Twój API jest po prostu klasą abstrakcyjną, która działa jak fabryka możesz również wdrożyć metodę wirtualną w różnych klasach i określić, z której instancji chcesz zadzwonić.