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.
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. –
+1 per l'uso della variazione "msg" dell'assert, rende l'errore di compilazione molto più leggibile! –