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ę.
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. –
+1 za użycie "msg" wersji asertywnej, sprawia, że błąd kompilacji jest znacznie bardziej czytelny! –