通过接口进行复制和交换

Copy-and-swap done through interfaces

本文关键字:复制 交换 接口      更新时间:2023-10-16

我正在尝试实现一个复制+交换习惯用法,以通过抽象级别实现强大的异常安全性,尽管原理很清楚,但细节往往是魔鬼。

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

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++并不是特别适合于基于继承的接口。例如,您正在实现一个使用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_assels和类型特征在没有继承的情况下对类进行静态类型检查:

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");

如果你问我,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"