我正在尝试实现一个复制+交换习惯用来通过抽象级别来实现强异常安全性,虽然原理很明确,但通常情况下魔鬼就是详细的。
说我有一个看起来像这样的课程:
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的用户来说是违反直觉的,因为他可能不会期待任何东西扔在那一点。
我想到的另一个选择是模板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。
有什么想法吗?
答案 0 :(得分:1)
如果你问我,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"
答案 1 :(得分: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对类进行静态类型检查,并在没有继承的情况下输入traits:
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");