【问题标题】:Copy-and-swap done through interfaces通过接口完成复制和交换
【发布时间】:2012-03-22 17:33:50
【问题描述】:

我正在尝试实现一个复制+交换习语,以通过一定程度的抽象来实现强异常安全性,尽管原理很明确,但通常情况下,魔鬼藏在细节中。

假设我有一个看起来像这样的类:

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_casts 都在引用上,这是一个在不支持类型时抛出的操作;这使我的对象处于良好状态,因为交换直到最后才会发生。但我不满意的是,调用 swappable1.Swap(swappable2) 仍然可以抛出(通过相同的 dynamic_cast 机制),这对于 Swap 的用户来说是违反直觉的,因为他可能不会期待任何事情在那个时候扔。

我想到的另一种方法是将 ISwappable 模板化,以便在 Swap 的实现中取消 dynamic_cast:

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 调用不被抛出(并允许我保证这两个对象在编译时实际上是可交换的),但现在的问题是我必须处理 DoSomething 中的具体类型,但是我无法访问该函数中的 AConcreteType。

有什么想法吗?

【问题讨论】:

    标签: c++ abstract-class copy-and-swap


    【解决方案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");
    

    【讨论】:

    • 这太棒了!我试图投票给你的答案,但显然我需要 15 或更高的声誉。如果我在DoSomething 操作的类型上模板DoSomething,我可以摆脱各种接口。我仍然可以保持我的单元可测试性,因为当我对AClass 进行单元测试时,我只是给它一个DummySomething。而且,如果传入的类型是可交换的(我猜,即有一个不抛出的赋值运算符),我可以立即调用 std::swap 。高超。非常感谢!
    【解决方案2】:

    如果你问我,ISwappable 的想法已经“不合时宜”,因为你不能毫无后果地将抽象类型相互交换……你可以安全交换的是接口地址(指针):

    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"
    

    【讨论】:

    • 我同意,这就是为什么在进行实际交换之前需要进一步检查以确保实现接口的具体类型相同,因为类实现接口这一事实显然不能保证这两个对象是兼容的: if( typedid( a ) != typeid( b ) ) { throw runtime_error( "Incompatible types" );顺便说一句,这使整个事情变得不那么可口和更笨重。
    • 是的,这不是由接口建模的好问题,因为您无法正确处理故障。有一些例子说明这是有道理的,例如与IComparable::Compare(IComparable* pOther) 然后typeid(this) == typeid(pOther) 是谓词为真的必要条件,或者您决定用于相等的任何代码。
    猜你喜欢
    • 2010-10-13
    • 2017-06-01
    • 1970-01-01
    • 2023-03-12
    • 2011-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多