2009-10-04 16 views
5

Mam do czynienia z problemami z projektowaniem mojej biblioteki C++. Jest to biblioteka do czytania strumieni obsługujących funkcję, której nie znalazłem w innych implementacjach "strumieniowych". Nie jest ważne, dlaczego zdecydowałem się zacząć to pisać. Chodzi o to, że mam klasę strumieniową, która zapewnia dwa ważne zachowania dzięki dziedziczeniu wielokrotnemu: współużytkowalność i możliwość zobaczenia.Dylemat wielokrotnego dziedziczenia w C++

Do strumieni udostępnianych należą te, które mają metodę shareBlock (size_t length), która zwraca nowy strumień, który współużytkuje zasoby ze strumieniem nadrzędnym (np. Przy użyciu tego samego bloku pamięci, który jest wykorzystywany przez strumień nadrzędny). Dostępne strumienie to te, które są ... dobrze widoczne. Poprzez metodę seek(), te klasy mogą wyszukiwać do danego punktu w strumieniu. Nie wszystkie strumienie biblioteki są udostępniane i/lub można je wyświetlić.

Klasa strumienia, która zapewnia implementację do wyszukiwania i współdzielenia zasobów, dziedziczy klasy interfejsu o nazwie Seekable and Shareable. To wszystko dobrze, jeśli znam typ takiego strumienia, ale czasami mogę chcieć, aby funkcja akceptowała jako argument strumień, który po prostu spełnia jakość bycia możliwym do wyświetlenia i współdzielenia w tym samym czasie, niezależnie od tego, którą klasę strumienia faktycznie jest. Mógłbym to zrobić, tworząc kolejną klasę, która dziedziczy zarówno Seekable i Shareable, jak i odwołując się do tego typu, ale wtedy musiałbym uczynić moje klasy, które są dostępne i dziedziczone po tej klasie. Gdyby dodać więcej "klas behawioralnych", takich jak te, musiałbym wprowadzić kilka modyfikacji w całym kodzie, wkrótce prowadząc do nieosiągalnego kodu. Czy istnieje sposób na rozwiązanie tego dylematu? Jeśli nie, to całkowicie rozumiem, dlaczego ludzie nie są zadowoleni z wielu dziedziczenia. To prawie wykonuje zadanie, ale właśnie wtedy nie: D

Każda pomoc jest doceniana.

- 2 edycja, preferowane rozwiązanie problemu -

Na początku myślałem Managu's rozwiązaniem byłoby mój preferowany jeden. Jednak Matthieu M. przyszedł z innym ja wolałem od Managu's: używać boost::enable_if<>. Chciałbym skorzystać z rozwiązania Managu, jeśli wygenerowane wiadomości nie byłyby tak przerażające. Gdyby istniał sposób na tworzenie pouczających komunikatów o błędach podczas kompilacji, z pewnością zrobiłbym to w ten sposób. Ale, jak powiedziałem, dostępne metody dają przerażające wiadomości. Więc wolę (znacznie) mniej pouczające, ale czystsze wiadomości produkowane, gdy warunki nie są spełnione.

Utworzyłem kilka makr, aby ułatwić zadanie napisać funkcje szablonów, które mają argumentów dziedziczenie wybierz typy klas, tu idą:

// SonettoEnableIfDerivedMacros.h 
#ifndef SONETTO_ENABLEIFDERIVEDMACROS_H 
#define SONETTO_ENABLEIFDERIVEDMACROS_H 

#include <boost/preprocessor/repetition/repeat.hpp> 
#include <boost/preprocessor/array/elem.hpp> 
#include <boost/mpl/bool.hpp> 
#include <boost/mpl/and.hpp> 
#include <boost/type_traits/is_base_and_derived.hpp> 
#include <boost/utility/enable_if.hpp> 

/* 
    For each (TemplateArgument,DerivedClassType) preprocessor tuple, 
    expand: `boost::is_base_and_derived<DerivedClassType,TemplateArgument>,' 
*/ 
#define SONETTO_ENABLE_IF_DERIVED_EXPAND_CONDITION(z,n,data) \ 
     boost::is_base_and_derived<BOOST_PP_TUPLE_ELEM(2,1,BOOST_PP_ARRAY_ELEM(n,data)), \ 
       BOOST_PP_TUPLE_ELEM(2,0,BOOST_PP_ARRAY_ELEM(n,data))>, 

/* 
    ReturnType: Return type of the function 
    DerivationsArray: Boost.Preprocessor array containing tuples in the form 
      (TemplateArgument,DerivedClassType) (see 
        SONETTO_ENABLE_IF_DERIVED_EXPAND_CONDITION) 

    Expands: 
    typename boost::enable_if< 
      boost::mpl::and_< 
        boost::is_base_and_derived<DerivedClassType,TemplateArgument>, 
        ... 
        boost::mpl::bool_<true> // Used to nullify trailing comma 
      >, ReturnType>::type 
*/ 
#define SONETTO_ENABLE_IF_DERIVED(ReturnType,DerivationsArray) \ 
     typename boost::enable_if< \ 
       boost::mpl::and_< \ 
         BOOST_PP_REPEAT(BOOST_PP_ARRAY_SIZE(DerivationsArray), \ 
          SONETTO_ENABLE_IF_DERIVED_EXPAND_CONDITION,DerivationsArray) \ 
         boost::mpl::bool_<true> \ 
      >, ReturnType>::type 

#endif 

// main.cpp: Usage example 
#include <iostream> 
#include "SonettoEnableIfDerivedMacros.h" 

class BehaviourA 
{ 
public: 
    void behaveLikeA() const { std::cout << "behaveLikeA()\n"; } 
}; 

class BehaviourB 
{ 
public: 
    void behaveLikeB() const { std::cout << "behaveLikeB()\n"; } 
}; 

class BehaviourC 
{ 
public: 
    void behaveLikeC() const { std::cout << "behaveLikeC()\n"; } 
}; 

class CompoundBehaviourAB : public BehaviourA, public BehaviourB {}; 
class CompoundBehaviourAC : public BehaviourA, public BehaviourC {}; 
class SingleBehaviourA : public BehaviourA {}; 

template <class MustBeAB> 
SONETTO_ENABLE_IF_DERIVED(void,(2,((MustBeAB,BehaviourA),(MustBeAB,BehaviourB)))) 
myFunction(MustBeAB &ab) 
{ 
    ab.behaveLikeA(); 
    ab.behaveLikeB(); 
} 

int main() 
{ 
    CompoundBehaviourAB ab; 
    CompoundBehaviourAC ac; 
    SingleBehaviourA a; 

    myFunction(ab); // Ok, prints `behaveLikeA()' and `behaveLikeB()' 
    myFunction(ac); // Fails with `error: no matching function for 
        // call to `myFunction(CompoundBehaviourAC&)'' 
    myFunction(a); // Fails with `error: no matching function for 
        // call to `myFunction(SingleBehaviourA&)'' 
} 

Jak widać, komunikaty o błędach są wyjątkowo czysty (przynajmniej w GCC 3.4.5). Ale mogą być mylące. Nie informuje, że przekazałeś niewłaściwy typ argumentu. Informuje on, że funkcja nie istnieje (i, w rzeczywistości, nie jest to spowodowane SFINAE, ale może to nie być dokładnie jasne dla użytkownika). Mimo to wolę te czyste wiadomości od tych, które produkują.

Jeśli znajdziesz jakieś błędy w tym kodzie, edytuj je i popraw, lub dodaj komentarz w tym zakresie. Jedną z głównych kwestii, które napotykam w tych makrach, jest to, że ograniczają się one do niektórych ograniczeń Boost.Preprocessor. Tutaj na przykład mogę tylko przekazać DerivationsArray z maksymalnie 4 pozycji do SONETTO_ENABLE_IF_DERIVED(). Myślę, że te limity są konfigurowalne, a może nawet zostaną zniesione w nadchodzącym standardzie C++ 1x, prawda? Proszę popraw mnie jeżeli się mylę. Nie pamiętam, czy zasugerowali zmiany w preprocesorze.

Dziękuję.

Odpowiedz

6

Spójrz na boost::enable_if

// Before 
template <class Stream> 
some_type some_function(const Stream& c); 

// After 
template <class Stream> 
boost::enable_if< 
    boost::mpl::and_< 
    boost::is_base_and_derived<Shareable,Stream>, 
    boost::is_base_and_derived<Seekable,Stream> 
    >, 
    some_type 
> 
some_function(const Stream& c); 

Dzięki SFINAE tej funkcji będą rozpatrywane tylko wtedy, gdy strumień spełnia wymóg, czyli tu czerpać z obu Shareable i możliwy do przeszukania.

0

Zakładając zarówno Seekable i Shareable mają wspólnego przodka, jeden sposób mogę myśleć próbuje przygnębiony (oczywiście assert s zastąpione swojej Sprawdzanie błędów):

void foo(Stream *s) { 
    assert(s != NULL); 
    assert(dynamic_cast<Seekable*>(s) != NULL); 
    assert(dynamic_cast<Shareable*>(s) != NULL); 
} 
12

Zaledwie kilka myśli:

STL ma taki sam problem z iteratorami i funktorami. Rozwiązaniem było zasadniczo usunięcie typów z równania razem, udokumentowanie wymagań (jako "pojęć") i użycie tego, co wystarcza do pisania kaczego. To dobrze pasuje do polityki polimorfizmu w czasie kompilacji.

Być może midground byłoby stworzenie funkcji szablonu, która statycznie sprawdza swoje warunki podczas tworzenia. Oto szkic (którego nie gwarantuję skompiluje).

class shareable {...}; 
class seekable {...}; 

template <typename StreamType> 
void needs_sharable_and_seekable(const StreamType& stream) 
{ 
    BOOST_STATIC_ASSERT(boost::is_base_and_derived<shareable, StreamType>::value); 
    BOOST_STATIC_ASSERT(boost::is_base_and_derived<seekable, StreamType>::value); 
    .... 
} 

Edit: spędził kilka minut upewniając rzeczy skompilowane i „oczyszczenie” komunikaty o błędach:

#include <boost/type_traits/is_base_and_derived.hpp> 
#include <boost/mpl/assert.hpp> 

class shareable {}; 
class seekable {}; 

class both : public shareable, public seekable 
{ 
}; 


template <typename StreamType> 
void dosomething(const StreamType& dummy) 
{ 
    BOOST_MPL_ASSERT_MSG((boost::is_base_and_derived<shareable, StreamType>::value), 
         dosomething_requires_shareable_stream, 
         (StreamType)); 
    BOOST_MPL_ASSERT_MSG((boost::is_base_and_derived<seekable, StreamType>::value), 
         dosomething_requires_seekable_stream, 
         (StreamType)); 
} 

int main() 
{ 
    both b; 
    shareable s1; 
    seekable s2; 
    dosomething(b); 
    dosomething(s1); 
    dosomething(s2); 
} 
+0

To na pewno dobry sposób na rozwiązanie tego problemu. Będę zabawiał się z kilkoma pomysłami, które miałem. Prawdopodobnie zaakceptuję twoją odpowiedź, kiedy skończę, a ja będę edytować moje pytanie za pomocą znalezionych przeze mnie rozwiązań. Dzięki. –

+0

+1 za użycie "msg" wersji asertywnej, sprawia, że ​​błąd kompilacji jest znacznie bardziej czytelny! –

1

Jak o użyciu metody szablonu?

template <typename STREAM> 
void doSomething(STREAM &stream) 
{ 
    stream.share(); 
    stream.seek(...); 
} 
+0

Po prostu za pomocą szablonów może sprawić, że rzeczy naprawdę niezrozumiałe. Interfejs tego, co jest potrzebne, jest używany, ale początkujący użytkownik biblioteki nie zauważyłby, do której klasy należał ten interfejs. Użycie 'boost :: enable_if' lub' BOOST_MPL_ASSERT' zgodnie z sugestiami poprawia pracę. –

+0

Ale nadal musisz obniżyć strumień przy użyciu parametru dynamic_cast <..>, ponieważ Twój parametr nadal jest strumieniem. Rozwiązanie szablonowe jest szybsze, ponieważ "obsada" jest wykonywana w czasie kompilacji. Pewnie, że nie jest to tak intuicyjne. – mmmmmmmm

+0

Nie. Rozwiązanie 'enable_if' nie wymaga rzutowania, ponieważ używa również szablonów podobnych do twojego, ale dodawane jest dodatkowe sprawdzanie poszczególnych typów. Wymagane typy nadal nie pojawiają się w komunikacie o błędzie, ale ludzie mogą przynajmniej znaleźć je w sygnaturze funkcji. Czytaj mój zredagowany post i przetestuj przykładowy kod, aby uzyskać więcej szczegółów. Myślę, że może się to opłacać. –

0

Zamień "możliwe do udostępnienia" i "widzialne" za pomocą "in" i "out" i znajdź swoje rozwiązanie "io". W bibliotece podobne problemy powinny mieć podobne rozwiązania.

Powiązane problemy