5

私のC++ライブラリの設計で問題に直面しています。これは、他の「ストリーム」実装では見つけられなかった機能をサポートするストリームを読み取るためのライブラリです。なぜ私がそれを書き始めることにしたのかはそれほど重要ではありません。重要なのは、多重継承を通じて2つの重要な動作を提供するストリームクラスがあるということです。共有可能性とシーク可能性です。

共有可能なストリームとは、親ストリームとリソースを共有する新しいストリームを返すshareBlock(size_t length)メソッドを持つストリームです(たとえば、親ストリームで使用されるのと同じメモリブロックを使用します)。シーク可能なストリームとは、シーク可能なストリームです。メソッドseek()を介して、これらのクラスはストリーム内の特定のポイントをシークできます。ライブラリのすべてのストリームが共有可能および/またはシーク可能であるとは限りません。

リソースのシークと共有の両方の実装を提供するストリームクラスは、SeekableおよびShareableと呼ばれるインターフェイスクラスを継承します。そのようなストリームのタイプを知っていればそれはすべて良いことですが、実際にどのストリームクラスに関係なく、同時にシーク可能で共有可能であるという品質を単に満たすストリームを引数として受け入れる関数が必要な場合がありますは。SeekableとShareableの両方を継承するさらに別のクラスを作成し、そのタイプへの参照を取得することはできますが、シーク可能で共有可能なクラスをそのクラスから継承する必要があります。このような「動作クラス」をさらに追加する場合は、コードのいたるところにいくつかの変更を加える必要があり、すぐに保守不可能なコードになります。このジレンマを解決する方法はありますか?そうでなければ、私は」m多重継承に人々が満足しない理由を、絶対に理解するようになります。それほとんど仕事をしますが、ちょうどその時、それはしません:D

どんな助けでも大歓迎です。

--2回目の編集、推奨される問題解決-

最初は、マナグの解決策が私の好みの解決策になると思いました。しかし、Matthieu M.には、Managuよりも私が好んだ別のものが付属していましたboost::enable_if<>BOOST_MPL_ASSERT生成されたメッセージがそれほど気味が悪い場合は、Managuのソリューションを使用したいと思います。有益なコンパイル時エラーメッセージを作成する方法があれば、私は確かにその方法を実行します。しかし、私が言ったように、利用可能なメソッドは不気味なメッセージを生成します。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それでも、私はそれらの生成物よりもそれらのクリーンなメッセージを好みます。

このコードにバグを見つけた場合は、それらを編集して修正するか、その点についてコメントを投稿してください。これらのマクロで私が見つけた1つの大きな問題は、それらがBoost.Preprocessorの制限に制限されていることです。ここでは、たとえば、に渡すことができるのDerivationsArrayは最大4つのアイテムのみSONETTO_ENABLE_IF_DERIVED()です。ただし、これらの制限は構成可能であり、今後のC++1x標準でも解除される可能性がありますね。間違っていたら訂正してください。彼らがプリプロセッサへの変更を提案したかどうかは覚えていません。

ありがとうございました。

4

6 に答える 6

12

ほんの少しの考え:

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);
}
于 2009-10-04T13:48:45.983 に答える
6

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の両方から派生している場合にのみ考慮されます。

于 2009-10-04T13:57:24.567 に答える
1

テンプレートメソッドを使用してみませんか?

template <typename STREAM>
void doSomething(STREAM &stream)
{
  stream.share();
  stream.seek(...);
}
于 2009-10-04T13:50:27.960 に答える
0

デコレータパターンが必要な場合があります。

于 2009-10-04T13:47:30.040 に答える
0

Seekable両方が共通の祖先を持っていると仮定すると、Shareable私が考えることができる1つの方法は、ダウンキャストしようとすることです(もちろん、はassertエラーチェックに置き換えられます)。

void foo(Stream *s) {
    assert(s != NULL);
    assert(dynamic_cast<Seekable*>(s) != NULL);
    assert(dynamic_cast<Shareable*>(s) != NULL);
}
于 2009-10-04T13:47:32.810 に答える
0

「shareable」と「seekable」を「in」と「out」に置き換えて、「io」ソリューションを見つけます。ライブラリでは、同様の問題に同様の解決策があるはずです。

于 2009-10-04T14:58:04.103 に答える