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_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 中的具体类型,但我没有t 可以访问该函数内的 AConcreteType。

有任何想法吗?

4

2 回答 2

1

如果你问我,a 的想法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"
于 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 回答