2014-10-21 32 views
11

Uczę się o mixins (w C++). Przeczytałem kilka artykułów o mixins i znalazłem dwa różne wzory "przybliżania" mixinów w C++.Dwa różne schematy mixin w C++. (mixin? CRTP?)

Wzór 1:

template<class Base> 
struct Mixin1 : public Base { 
}; 

template<class Base> 
struct Mixin2 : public Base { 
}; 

struct MyType { 
}; 

typedef Mixin2<Mixin1<MyType>> MyTypeWithMixins; 

Wzór 2: (może się nazywać CRTP)

template<class T> 
struct Mixin1 { 
}; 

template<class T> 
struct Mixin2 { 
}; 

struct MyType { 
}; 

struct MyTypeWithMixins : 
    public MyType, 
    public Mixin1<MyTypeWithMixins>, 
    public Mixin2<MyTypeWithMixins> { 
}; 

są one równoważne praktycznie? Chciałbym poznać praktyczną różnicę między wzorami.

Odpowiedz

7

Różnica jest widoczność. W pierwszym wzorcu, elementy MyType są bezpośrednio widoczne i użyteczne przez mixy, bez potrzeby rzutowania, a elementy Mixin1 są widoczne dla Mixin2. Jeśli MyType chce uzyskać dostęp do elementów z miksów, musi obsłużyć this i nie jest to świetny sposób, aby zrobić to bezpiecznie.

W drugim wzorze nie ma automatycznej widoczności między typem a mixinsem, ale mixiny mogą bezpiecznie i łatwo odrzucić this do MyTypeWithMixins, a tym samym uzyskać dostęp do elementów typu i innych elementów. (MyType może również, jeśli zastosowałeś do niego również CRTP.)

Sprowadza się to do wygody i elastyczności. Jeśli twoje mixiny mają dostęp wyłącznie do usług tego typu i nie mają własnych zależności między siostrami, pierwszy wzór jest ładny i prosty. Jeśli mixin zależy od usług dostarczanych przez typ lub inne mixiny, jesteś mniej lub bardziej zmuszony do używania drugiego wzoru.

7

Czy są one równoważne praktycznie? Chciałbym poznać praktyczną różnicę między wzorami.

Są różne koncepcyjnie.

Dla pierwszego wzoru masz dekoratory (przezroczyste) nad podstawową klasą funkcjonalności, z których każda dodaje własną wersję/specjalizację do istniejącej implementacji.

Związek Pierwsze modele wzór „oznacza A” (MyTypeWithMixins jest Mixin1<MyType> specjalizacji Mixin1<MyType> jest MyType kierunek).

Jest to dobre podejście w przypadku implementowania funkcjonalności w sztywnym interfejsie (ponieważ wszystkie typy będą implementować ten sam interfejs).

W przypadku drugiego wzoru części funkcjonalne są używane jako szczegóły implementacji (prawdopodobnie w obrębie różnych, niepowiązanych klas).

Relacja wzorowana jest tu „realizowany jest w kategoriach” (MyTypeWithMixins jest MyType specjalizacja, realizowane pod względemMixin1 i Mixin2 funkcjonalności). W wielu implementacjach CRTP baza szablonowa CRTP jest dziedziczona jako prywatna lub chroniona.

Jest to dobre podejście w przypadku wdrażania wspólnej funkcjonalności za pośrednictwem różnych, niepowiązanych ze sobą komponentów (tj. Bez tego samego interfejsu).Dzieje się tak, ponieważ dwie klasy dziedziczące z Mixin1 będą , a nie mają tę samą klasę podstawową.

Aby zapewnić konkretny przykład dla każdego:

W pierwszym przypadku, należy rozważyć modelowanie biblioteki GUI. Każda kontrola wizualna miałaby (na przykład) funkcję display, która w ScrollableMixin mogłaby dodać paski przewijania, jeśli jest to wymagane; Wstawka przewijania byłaby klasą bazową dla większości kontroli, które są ponownie spory (ale wszystkie z nich stanowi część „Sterowanie/komponent wizualny/wyświetlanej” klasowej hierarchii.

class control { 
    virtual void display(context& ctx) = 0; 
    virtual some_size_type display_size() = 0; 
}; 

template<typename C>class scrollable<C>: public C { // knows/implements C's API 
    virtual void display(context& ctx) override { 
     if(C::display_size() > display_size()) 
      display_with_scrollbars(ctx); 
     else 
      C::display(canvas); 
    } 
    ... 
}; 

using scrollable_messagebox = scrollable<messagebox>; 

W tym przypadku, wszystkie mixin typy zastąpi (na przykład) metodę wyświetlania i przekaże części jej funkcjonalności (specjalistyczna część rysunku) do dekorowanego typu (bazy). do dodania numeru wersji do serializowanych obiektów w aplikacji. Implementacja będzie wyglądać następująco:

template<typename T>class versionable<T> { // doesn't know/need T's API 
    version_type version_; 
protected: 
    version_type& get_version(); 
}; 

class database_query: protected versionable<database_query> {}; 
class user_information: protected versionable<user_information> {}; 

W tym przypadku zarówno database_query, jak i user_information zapisują swoje ustawienia z numerem wersji, ale nie są w żaden sposób w tej samej hierarchii obiektów (nie mają wspólnej podstawy).

+0

Do tego nie służy CRTP. Oczywiście, CRTP obejmuje ogólną strukturę 'class Child: Parent ', ale chodzi o to, że klasa bazowa - 'Parent ', wie o klasie potomnej 'Child' (jak? To jest parametr szablonu!) Parent can następnie odwołaj rzeczy zdefiniowane w samym dziecku - np. może stworzyć operatora! = od operatora klasy dziecka ==. –

+0

@Hwalters, niekoniecznie. W powyższym przykładzie, implementacja 'wersji ' nie nakłada żadnych ograniczeń na 'T' (to znaczy, że nie musi nic o nim wiedzieć). Dokonano tego celowo. – utnapistim

+0

Myliłem się; użycie 'Child' do utworzenia instancji' Parent' po prostu jako wygodnego uniquifier dla typu podstawowego ma narzędzie. Ale myślę, że argumentowałeś niewłaściwie - nie nakładanie ograniczeń na "T" jest niewystarczające, by uzasadnić użycie CRTP. W szczególności zmagam się z twoim przykładem; Końcowym rezultatem jest to, że obiekty 'database_query' i' user_information' mają członków version_ i get_version, nawet tych samych typów. Jaką przewagę zapewnia używanie CRTP do ich wstrzykiwania? (Na przykład, klasy bazowe _ są_ różnymi typami, ale w jaki sposób miałoby to znaczenie?) –