Дилемма множественного наследования в C ++ - PullRequest
5 голосов
/ 04 октября 2009

У меня проблемы с дизайном моей библиотеки C ++. Это библиотека для чтения потоков, которая поддерживает функцию, которую я не нашел в других реализациях «потоков». Не очень важно, почему я решил начать писать. Дело в том, что у меня есть потоковый класс, который обеспечивает два важных поведения через множественное наследование: совместимость и возможность поиска.

Совместно используемые потоки - это те, у которых есть метод shareBlock (size_t length), который возвращает новый поток, который совместно использует ресурсы с его родительским потоком (например, используя тот же блок памяти, который использовался родительским потоком). Поисковые потоки - это те, которые ... ну, можно искать. Посредством метода seek () эти классы могут искать заданную точку в потоке. Не все потоки библиотеки доступны для совместного использования и / или поиска.

Потоковый класс, который обеспечивает реализацию поиска и совместного использования ресурсов, наследует классы интерфейса, называемые Seekable и Shareable. Это хорошо, если я знаю тип такого потока, но иногда мне может потребоваться, чтобы функция принимала в качестве аргумента поток, который просто отвечает требованиям поиска и совместного использования независимо от того, к какому классу потока это относится. является. Я мог бы сделать это, создав еще один класс, который наследует и Seekable, и Shareable, и взяв ссылку на этот тип, но тогда я должен был бы сделать так, чтобы мои классы, которые являются и доступными для поиска и разделяемыми, наследовали от этого класса. Если бы было добавлено больше «поведенческих классов», подобных тем, мне нужно было бы внести несколько изменений в код повсюду, что вскоре привело бы к неуправляемому коду. Есть ли способ решить эту дилемму? Если нет, то я абсолютно понимаю, почему люди не удовлетворены множественным наследованием. Он почти делает свою работу, но только тогда этого не делает: D

Любая помощь приветствуется.

- 2-е редактирование, предпочтительное решение проблемы -

Сначала я подумал, что Манагу будет моим предпочтительным решением. Тем не менее, Матье М. пришел с другим, который я предпочел Манагу: использовать boost::enable_if<>. Я хотел бы использовать решение Манагу, если BOOST_MPL_ASSERT производил сообщения не были такими жуткими. Если бы был какой-нибудь способ создать поучительные сообщения об ошибках во время компиляции, я бы, конечно, поступил так. Но, как я уже сказал, доступные методы производят жуткие сообщения. Поэтому я предпочитаю (намного) менее поучительное, но более чистое сообщение, генерируемое, когда boost::enable_if<> условия не выполнены.

Я создал несколько макросов, чтобы упростить задачу по написанию шаблонных функций, которые принимают аргументы, наследующие выбранные типы классов, вот они:

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

Как видите, сообщения об ошибках исключительно чистые (по крайней мере в GCC 3.4.5). Но они могут вводить в заблуждение. Он не сообщает вам, что вы передали неправильный тип аргумента. Он сообщает вам, что функция не существует (и, на самом деле, это не из-за SFINAE; но это может быть не совсем понятно пользователю). Тем не менее, я предпочитаю эти чистые сообщения, чем те, которые randomStuff ... ************** garbage ************** BOOST_MPL_ASSERT производит.

Если вы обнаружите какие-либо ошибки в этом коде, пожалуйста, отредактируйте и исправьте их или оставьте комментарий на этот счет. Одна из основных проблем, которые я обнаружил в этих макросах, заключается в том, что они ограничены некоторыми ограничениями Boost.Preprocessor. Здесь, например, я могу передать только DerivationsArray из 4 элементов в SONETTO_ENABLE_IF_DERIVED(). Я думаю, что эти ограничения можно настроить, и, возможно, они даже будут отменены в будущем стандарте C ++ 1x, не так ли? Пожалуйста, поправьте меня, если я ошибаюсь. Я не помню, предлагали ли они изменения в препроцессоре.

Спасибо.

Ответы [ 6 ]

12 голосов
/ 04 октября 2009

Всего несколько мыслей:

У STL такая же проблема с итераторами и функторами. Решение в основном заключалось в том, чтобы удалить все типы из уравнения вместе, задокументировать требования (как «концепции») и использовать то, что равнозначно типизации утки. Это хорошо вписывается в политику полиморфизма во время компиляции.

Возможно, в центре внимания будет создание шаблонной функции, которая статически проверяет свои условия при создании экземпляра. Вот эскиз (который я не гарантирую, скомпилирует).

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

Редактировать: Потратить несколько минут, чтобы убедиться, что все скомпилировано, и "очистить" сообщения об ошибках:

#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);
}
6 голосов
/ 04 октября 2009

Взгляните на 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);

Благодаря SFINAE эта функция будет рассматриваться только в том случае, если Stream удовлетворяет требованию, т. Е. Здесь происходит как из Shareable, так и Seekable.

1 голос
/ 04 октября 2009

Как насчет использования шаблонного метода?

template <typename STREAM>
void doSomething(STREAM &stream)
{
  stream.share();
  stream.seek(...);
}
0 голосов
/ 04 октября 2009

Замените «разделяемый» и «доступный» на «входящий» и «выходной» и найдите решение «io». В библиотеке похожие проблемы должны иметь похожие решения.

0 голосов
/ 04 октября 2009

Если предположить, что и Seekable, и Shareable имеют общего предка, я могу придумать один способ попытаться снизить рейтинг (конечно, assert s заменяется проверкой ошибок):

void foo(Stream *s) {
    assert(s != NULL);
    assert(dynamic_cast<Seekable*>(s) != NULL);
    assert(dynamic_cast<Shareable*>(s) != NULL);
}
0 голосов
/ 04 октября 2009

Возможно, вам понадобится шаблон Декоратор .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...