2009-10-04 35 views
5

Tengo problemas con el diseño de una biblioteca C++. Es una biblioteca para leer secuencias que admiten una función que no he encontrado en otras implementaciones de "transmisión". No es realmente importante por qué he decidido comenzar a escribirlo. El punto es que tengo una clase de flujo que proporciona dos comportamientos importantes a través de la herencia múltiple: compartibilidad y capacidad de búsqueda.Dilema de herencia múltiple en C++

Las secuencias compartibles son aquellas que tienen un método shareBlock (size_t length) que devuelve una nueva transmisión que comparte recursos con su secuencia principal (por ejemplo, utilizando el mismo bloque de memoria utilizado por la secuencia principal). Las corrientes buscables son aquellas que son ... bueno, buscables. A través de un método seek(), estas clases pueden buscar un punto dado en la secuencia. No todas las secuencias de la biblioteca se pueden compartir o buscar.

Una clase de flujo que proporciona implementación para buscar y compartir recursos hereda las clases de interfaz llamadas Seekable y Shareable. Eso es bueno si conozco el tipo de transmisión, pero a veces deseo una función que acepte como argumento una secuencia que simplemente cumpla con la calidad de ser buscable y compartible al mismo tiempo, independientemente de la clase de transmisión en realidad. es. Podría hacer eso creando otra clase que herede tanto Seekable como Shareable y tomando una referencia a ese tipo, pero luego tendría que hacer que mis clases sean tanto heredables como compartibles heredadas de esa clase. Si se agregasen más "clases de comportamiento" como esas, necesitaría hacer varias modificaciones en todas partes del código, lo que pronto llevaría a un código que no se puede mantener. ¿Hay alguna manera de resolver este dilema? Si no, entiendo por qué las personas no están satisfechas con la herencia múltiple. Es casi hace el trabajo, pero, en ese momento, no: D

Cualquier ayuda es apreciada.

- 2a Edición, resolución de problemas preferido -

Al principio pensé Managu's solución sería mi preferida. Sin embargo, Matthieu M. vino con otro que preferí sobre el de Managu: usar boost::enable_if<>. Me gustaría usar la solución de Managu si los mensajes producidos por BOOST_MPL_ASSERT no fueran tan espeluznantes. Si hubiera alguna forma de crear mensajes de error instructivos en tiempo de compilación, seguramente lo haría de esa manera. Pero, como dije, los métodos disponibles producen mensajes espeluznantes. Así que prefiero el mensaje (mucho) menos instructivo, pero más limpio, que se produce cuando las condiciones boost::enable_if<> no se cumplen.

que he creado algunas macros para facilitar la tarea de escribir las funciones de plantilla que toman argumentos que heredan seleccione los tipos de clase, aquí van:

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

Como se puede ver, los mensajes de error son excepcionalmente limpia (al menos en GCC 3.4.5). Pero pueden ser engañosos. No le informa que ha pasado el tipo de argumento incorrecto. Le informa que la función no existe (y, de hecho, no se debe a SFINAE, pero puede no ser exactamente clara para el usuario). Aún así, prefiero esos mensajes limpios sobre esos randomStuff ... ************** garbage **************BOOST_MPL_ASSERT produce.

Si encuentra algún error en este código, edítelo y corríjalo, o publique un comentario al respecto. El único problema importante que encuentro en esas macros es que están limitadas a algunos límites de Boost.Preprocessor. Aquí, por ejemplo, solo puedo pasar un DerivationsArray de hasta 4 elementos a SONETTO_ENABLE_IF_DERIVED(). Creo que esos límites son configurables, y tal vez incluso se levantarán en el próximo estándar C++ 1x, ¿no? Por favor corrígeme si estoy equivocado. No recuerdo si sugirieron cambios al preprocesador.

Gracias.

Respuesta

6

Tome un vistazo 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); 

Gracias a SFINAE sólo se considerará esta función si la corriente satisface el requisito, es decir, aquí se derivan de tanto compartibles y Seekable.

0

Suponiendo tanto Seekable y Shareable tienen ancestro común, de una manera que se me ocurre es tratar de abatidos (por supuesto, assert s substituir por su comprobación de errores):

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

A pocos pensamientos:

STL tiene este mismo tipo de problema con iteradores y funtores. La solución consistía básicamente en eliminar todos los tipos de la ecuación, documentar los requisitos (como "conceptos") y usar lo que equivale a escribir a pato. Esto encaja bien con una política de polimorfismo en tiempo de compilación.

Quizás un punto intermedio sea crear una función de plantilla que compruebe estáticamente sus condiciones en la creación de instancias. Aquí hay un boceto (que no garantizo compilará).

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); 
    .... 
} 

Editar: pasó unos minutos asegurándose de que todo compilados, y "limpieza" de los mensajes de error:

#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

Esto parece ser una buena forma de solucionarlo. Juguetearé con algunas ideas que he tenido. Probablemente acepte su respuesta cuando haya terminado y editaré mi pregunta con las soluciones que he encontrado. Gracias. –

+0

+1 para el uso de la variación 'msg' de la declaración, ¡hace que el error de compilación sea mucho más legible! –

1

Cómo sobre el uso de un método de la plantilla?

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

El simple hecho de usar plantillas puede hacer que las cosas no sean realmente intuitivas. Se utiliza la interfaz de lo que se quiere, pero un usuario novato de la biblioteca no se daría cuenta de a qué clase pertenecía esa interfaz. Usar 'boost :: enable_if' o' BOOST_MPL_ASSERT' como se sugiere mejora el trabajo. –

+0

Pero aún debe descifrar la transmisión utilizando un dynamic_cast <..> porque su parámetro sigue siendo una transmisión. La solución de plantilla es más rápida porque el "lanzamiento" se realiza en tiempo de compilación. Claro que no es tan intuitivo. – mmmmmmmm

+0

No. La solución 'enable_if' no requiere conversión porque también usa plantillas, como la suya, pero se agrega una verificación adicional por tipo. Los tipos necesarios aún no aparecen en el mensaje de error, pero las personas al menos pueden encontrarlos en la firma de la función. Lea mi publicación editada y pruebe el código de muestra para obtener más detalles, creo que puede valer la pena. –

0

Reemplace 'compartible' y 'buscable' por 'in' y 'out' y encuentre su solución 'io'. En una biblioteca, problemas similares deberían tener soluciones similares.