Pergunta

I'm trying to implement a copy+swap idiom to achieve strong-exception safety through a level of abstraction and, although the principle is clear, as it's often the case the devil is in the detail.

Say I have a class that looks like this:

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

I can now do this within a method that only deals with 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 );
}

where

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

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

This all works, as all the dynamic_casts are on references, which is an operation that throws when the type is not supported; this leaves my objects in a good state as the swap doesn't happen until the very end. But what I'm not comfortable with is the fact that the call swappable1.Swap(swappable2) can still throw (via the same dynamic_cast mechanism), and that would be counter-intuitive for the user of Swap as he would probably not expect anything to throw at that point.

An alternative I thought of was to template ISwappable so as to do away with the dynamic_cast inside the implementation of Swap:

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

so that its implementation is simply

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

This allows the Swap call to be non-throw (and allows me to guarantee that the two objects are actually swappable at compile-time), but the problem now is that I have to deal with a concrete type inside DoSomething, but I don't have access to AConcreteType inside that function.

Any ideas?

Foi útil?

Solução

C++ isn't particularly geared toward inheritance based interfaces. For example, you're implementing a function that takes an ISomething, but it also expects the object to be an ISwappable. Languages geared toward using interfaces like this usually have a direct way to express requirements for multiple interfaces on a single type.

Instead it's probably better in C++ to use templates and then express requirements on those template parameters when necessary. Static assertions and type traits are a pretty simple and readable way of doing this in 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);
}

You might also want to move away from using inheritance for interfaces altogether. You can usually get the static type checking on your classes via static_asserts and type traits without inheritance:

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

Outras dicas

If you ask me, the idea of a ISwappable is already "ill-posed" since you cannot swap abstract types into each other without consequences... What you can swap safely are addresses of interfaces (pointers):

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"
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top