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.
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. –
+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! –