2

私はコピー+スワップのイディオムを実装して、抽象化のレベルを通じて強力な例外の安全性を実現しようとしています。原則は明らかですが、悪魔は詳細にあることがよくあります。

次のようなクラスがあるとします。

class AConcreteType : 
    public ISomething,
    public ISwappable
{
public:
    // From ISwappable
    void Swap( ISwappable& );
};

ISomethingのみを処理するメソッド内でこれを実行できるようになりました。

void AClass::DoSomething( ISomething& something )
{
    // say there is a function that allows me to clone 'something'
    // Probably it ought to go into an auto_ptr, but for clarity:
    ISomething& somethingElse( clone( something ) );

    // ... so that at the end, after doing stuff with somethingElese I can do
    ISwappable& swappable1 = dynamic_cast<ISwappable&>( something );
    ISwappable& swappable2 = dynamic_cast<ISwappable&>( somethingElse );

    // ... I may want to check that the concrete types behind the interface are
    // actually the same too with something like typeid, but I'll leave that out for clarity

    swappable1.Swap( swappable2 );
}

どこ

void AConcreteType::Swap( ISwappable& swappable )
{
    AConcreteType& somethingConcrete = dynamic_cast<AConcreteType&>(swappable);

    std::swap( *this, somethingConcrete );
}

すべてのdynamic_castが参照上にあるため、これはすべて機能します。これは、タイプがサポートされていない場合にスローされる操作です。スワップは最後まで行われないため、これによりオブジェクトは良好な状態になります。しかし、私が気に入らないのは、swappable1.Swap(swappable2)の呼び出しが(同じdynamic_castメカニズムを介して)スローできるという事実です。これは、Swapのユーザーにとって、おそらく何も期待しないため、直感に反します。その時点で投げる。

私が考えた別の方法は、Swapの実装内のdynamic_castを廃止するためにISwappableをテンプレート化することでした。

template< typename T >
class ISwappable
{
public:
    virtual void Swap( T& ) = 0;
};

そのため、その実装は単純です

class AConcreteType :
    public ISomething,
    public ISwappable<AConcreteType>
{
    void Swap( AConcreteType& act ) { std::swap( *this, act ); }
};

これにより、Swap呼び出しを非スローにすることができます(また、コンパイル時に2つのオブジェクトが実際にスワップ可能であることを保証できます)が、問題は、DoSomething内で具象型を処理する必要があることですが、その関数内のAConcreteTypeにアクセスできます。

何か案は?

4

2 に答える 2

1

私に言わせれば、ISwappable結果なしに抽象型を相互に交換することはできないので、aの考え方はすでに「不適切」です...安全に交換できるのはインターフェース(ポインター)のアドレスです。

std::unique_ptr<ISomething> tI1(new AConcreteType(1)), tI2(new BConcreteType(2));
std::cout << tI1->IdentifyYourSelf() << std::endl; // -> prints "1"
std::cout << tI2->IdentifyYourSelf() << std::endl; // -> prints "2"
tI1.swap(tI2);
// contents are swapped now
std::cout << tI1->IdentifyYourSelf() << std::endl; // -> prints "2"
std::cout << tI2->IdentifyYourSelf() << std::endl; // -> prints "1"
于 2012-03-22T18:45:19.740 に答える
1

C++ は、継承ベースのインターフェイスを特に対象としていません。たとえば、ISomething を受け取る関数を実装していますが、オブジェクトが ISwappable であることも想定しています。このようなインターフェイスを使用することを目的とした言語には、通常、単一の型で複数のインターフェイスの要件を表現する直接的な方法があります。

代わりに、C++ でテンプレートを使用し、必要に応じてそれらのテンプレート パラメーターの要件を表現する方がよいでしょう。静的アサーションと型特性は、C++ でこれを行う非常に単純で読みやすい方法です。

template<typename T,typename Interface>
struct implements {
    static constexpr bool value = std::is_base_of<Interface,T>::value;
}

template<typename T>
void AClass::DoSomething(T &something ) {
    static_assert(implements<T,ISomething>::value, "requires ISomething");
    static_assert(implements<T,ISwappable<T>>::value, "requires ISwappable");

    T somethingElse = clone(something);

    something.Swap(somethingElse);
}

また、インターフェイスの継承を完全に使用しないようにすることもできます。通常、継承なしで static_asserts と型特性を介してクラスの静的型チェックを取得できます。

template<typename T>
struct is_swappable { static constexpr bool value = ... };

class AConcreteType {
    ...
};
static_assert(is_swappable<AConcreteType>,"...");

template<typename T>
void AClass::DoSomething(T &something ) {
    static_assert(is_something<T>::value, "requires something");
    static_assert(is_swappable<T>::value, "requires swappable");
于 2012-03-22T22:12:44.647 に答える