Копирование и замена через интерфейсы - PullRequest
2 голосов
/ 22 марта 2012

Я пытаюсь реализовать идиому copy + swap для достижения безопасности с сильными исключениями через уровень абстракции и, хотя принцип ясен, как это часто бывает, дьявол кроется в деталях.

* 1002Скажем, у меня есть класс, который выглядит следующим образом:
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, чтобы покончить с dynamic_cast внутри реализации Swap:

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 внутри этой функции.

Есть идеи?

Ответы [ 2 ]

1 голос
/ 23 марта 2012

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");
1 голос
/ 22 марта 2012

Если вы спросите меня, идея 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"
...