2009-10-04 11 views
5

Ho problemi con la progettazione di una mia libreria C++. È una libreria per la lettura di flussi che supportano una funzionalità che non ho trovato su altre implementazioni "stream". Non è davvero importante il motivo per cui ho deciso di iniziare a scriverlo. Il punto è che ho una classe di flusso che fornisce due comportamenti importanti attraverso l'ereditarietà multipla: condivisibilità e ricercabilità.Problema di ereditarietà multipla in C++

I flussi condivisibili sono quelli che dispongono di un metodo shareBlock (size_t length) che restituisce un nuovo flusso che condivide le risorse con il flusso principale (ad esempio, utilizzando lo stesso blocco di memoria utilizzato dallo stream principale). I flussi ricercabili sono quelli che sono ... beh, ricercabili. Attraverso un metodo seek(), queste classi possono cercare un determinato punto nel flusso. Non tutti i flussi della libreria sono condivisibili e/o ricercabili.

Una classe di flusso che fornisce entrambe l'implementazione per la ricerca e la condivisione di risorse eredita le classi di interfaccia denominate Seekable e condivisibili. Va bene se conosco il tipo di un tale stream, ma, a volte, potrei desiderare che una funzione accetti come argomento un flusso che soddisfa semplicemente la qualità di essere ricercabile e condivisibile allo stesso tempo, indipendentemente da quale classe di stream effettivamente sia è. Potrei farlo creando un'altra classe che eredita sia Seekable e Shareable che prendendo un riferimento a quel tipo, ma poi dovrei rendere le mie classi che sono sia ricercabili che condivisibili ereditate da quella classe. Se venissero aggiunte più "classi comportamentali" come queste, dovrei apportare molte modifiche ovunque nel codice, portando presto a codice non gestibile. C'è un modo per risolvere questo dilemma? Se no, allora sto assolutamente venendo a capire perché le persone non sono soddisfatte dall'ereditarietà multipla. È quasi il fa il lavoro, ma, solo allora, non lo fa: D

Qualsiasi aiuto è apprezzato.

- 2 ° modificare, preferito la risoluzione dei problemi -

In un primo momento ho pensato Managu's soluzione potrebbe essere il mio preferito uno. Tuttavia, Matthieu M. è venuto con un altro che ho preferito su Managu: per utilizzare boost::enable_if<>. Vorrei utilizzare la soluzione di Managu se i messaggi prodotti da BOOST_MPL_ASSERT non fossero così inquietanti. Se esistesse un modo per creare messaggi di errore istruttivi in ​​fase di compilazione, farei sicuramente così. Ma, come ho detto, i metodi disponibili producono messaggi inquietanti. Quindi preferisco il (molto) meno istruttivo, ma più pulito messaggio prodotto quando le condizioni di boost::enable_if<> non sono soddisfatte.

Ho creato alcune macro per facilitare il compito di scrivere le funzioni di template che accettano argomenti che ereditano tipi di classe selezionare, qui vanno:

// 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&)'' 
} 

Come si può vedere, i messaggi di errore sono eccezionalmente pulito (almeno in GCC 3.4.5). Ma possono essere fuorvianti. Non ti informa che hai passato il tipo di argomento sbagliato. Ti informa che la funzione non esiste (e, in effetti, non è dovuta a SFINAE, ma potrebbe non essere esattamente chiara all'utente). Tuttavia, preferisco quei messaggi puliti su quelli che producono randomStuff ... ************** garbage **************BOOST_MPL_ASSERT.

Se si riscontrano errori in questo codice, si prega di modificarli e correggerli, o pubblicare un commento al riguardo. Il problema principale che ho riscontrato in queste macro è che sono limitati a alcuni limiti di Boost.Preprocessor. Qui, ad esempio, posso passare solo un DerivationsArray di un massimo di 4 elementi a SONETTO_ENABLE_IF_DERIVED(). Penso che questi limiti siano configurabili, e forse saranno addirittura revocati nel prossimo standard C++ 1x, vero? Per favore correggimi se sbaglio. Non ricordo se hanno suggerito modifiche al preprocessore.

Grazie.

risposta

6

Date un'occhiata a 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); 

Grazie alla SFINAE sarà considerato questa funzione solo se soddisfa i requisiti flusso, vale a dire qui derivano sia da condivisibili e posizionabili.

0

Supponendo che sia Seekable e Shareable hanno antenato comune, in un modo mi viene in mente sta cercando di downcast (naturalmente, assert s sostituito con il controllo degli errori):

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

A pochi pensieri:

STL ha lo stesso tipo di problema con iteratori e funtori. La soluzione consisteva fondamentalmente nel rimuovere tutti i tipi dall'equazione, documentare i requisiti (come "concetti") e utilizzare ciò che equivale a digitarne la digitazione. Questo si adatta bene a una politica di polimorfismo in fase di compilazione.

Forse un campo centrale sarebbe creare una funzione di modello che controlla staticamente le sue condizioni all'istanziazione. Ecco uno schizzo (che non garantisco verrà compilato).

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: trascorso alcuni minuti assicurandosi che le cose compilati, e "ripulire" i messaggi di errore:

#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

Questo sicuramente sembra un buon modo per farlo. Mi divertirò con alcune idee che ho avuto. Probabilmente accetterò la tua risposta quando avrò finito e modificherai la mia domanda con le soluzioni che ho trovato. Grazie. –

+0

+1 per l'uso della variazione "msg" dell'assert, rende l'errore di compilazione molto più leggibile! –

1

ne dite di usare un metodo di modello?

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

Utilizzare semplicemente i modelli può rendere le cose davvero non intuitive. Viene utilizzata l'interfaccia di ciò che si desidera, ma un utente di una libreria inesperta non noterebbe da quale classe apparteneva quell'interfaccia. Usare 'boost :: enable_if' o' BOOST_MPL_ASSERT' come suggerito fa meglio il lavoro. –

+0

Tuttavia, devi ancora eseguire il downcast dello stream utilizzando un dynamic_cast <..> perché il tuo parametro è ancora un flusso. La soluzione modello è più veloce perché il "cast" è fatto in fase di compilazione. Certo non è così intuitivo. – mmmmmmmm

+0

No. La soluzione 'enable_if' non richiede cast perché utilizza anche modelli, proprio come i tuoi, ma viene aggiunta un'ulteriore verifica per tipo. I tipi richiesti non vengono ancora visualizzati nel messaggio di errore, ma le persone possono trovarli almeno nella firma della funzione. Leggi il mio post modificato e prova il codice di esempio per maggiori dettagli, penso che possa valerne la pena. –

0

Sostituisci "condivisibile" e "cercabile" con "in" e "fuori" e trova la soluzione "io". In una biblioteca simili problemi dovrebbero avere soluzioni simili.