Улучшение безопасности паттерна Клон - PullRequest
2 голосов
/ 10 июля 2019

Если кто-то хочет реализовать шаблон Clone в C ++, он может быть не уверен в безопасности, потому что производный класс может забыть переопределить его:

struct A {
    virtual A* Clone() const {
        return new A(*this);
    }
}

struct B : A {
    int value;
};

int main() {
   B b;
   // oops
   auto b_clone = b.Clone();
   delete b_clone;
}

Каковы возможные пути улучшения паттерна Clone в C ++ в этом отношении?

Был задан более общий вопрос: Заставление производного класса перегрузить виртуальный метод в неабстрактном базовом классе

Однако это кажется слишком общим, чтобы иметь хорошее решение в C ++ - речь идет о возможных способах принудительного переопределения метода. Меня больше интересует поиск полезного шаблона, который может помочь в конкретном случае использования шаблона Cloneable.

Ответы [ 2 ]

0 голосов
/ 11 июля 2019

C ++ 17 и новее предлагает std::any. Таким образом, теоретически вы можете создать функцию-клон, которая вместо этого возвращает std::any*:

struct A {
    virtual std::any* Clone() const {
        return new A(*this);
    }
}

struct B : A {
    int value;
    // I suppose it doesn't have to be virtual here, 
    // but just in case we want to inherit the cloning capability from B as well
    virtual std::any* Clone() const { // Note: you still need to override this function
        return new B(*this);          // in the lower levels, though
    }
};
// Note: I'm still on MSVS2010, so this C++17 code is untested.
// Particularly problematic could be this main
int main() {
   B b;
   // Here is the clone
   auto b_clone = std::any_cast<B*>(b.Clone());
   delete b_clone;
}

Опять же, это не проверено, но теоретически это должно работать.

0 голосов
/ 10 июля 2019

Это разработка одного из ответов, предлагающая проверку во время выполнения с использованием typeid: Заставление производного класса перегрузить виртуальный метод в неабстрактном базовом классе

Используя CRTP, можно прийти к следующей основной идее:

Создайте класс Cloneable<Derived>, который управляет клонированием для Derived и добавляет все необходимые проверки во время выполнения (кажется, что проверки во время компиляции невозможны даже с CRTP).

Однако, это не тривиально, и нужно также управлять наследованием через Cloneable, как описано:

#include <memory>
#include <cassert>
#include <type_traits>
#include <typeinfo>

class CloneableInterface {
public:
    virtual std::unique_ptr<CloneableInterface> Clone() const = 0;
};

template <class... inherit_from>
struct InheritFrom : public inherit_from... {
};

template <class Derived, class AnotherBase = void, bool base_is_cloneable = std::is_base_of_v<CloneableInterface, AnotherBase>>
class Cloneable;

// three identical implementations, only the inheritance is different

// "no base is defined" case
template <class Derived>
class Cloneable<Derived, void, false> : public CloneableInterface {
public:
    std::unique_ptr<CloneableInterface> Clone() const override {
        assert(typeid(*this) == typeid(Derived));
    return std::make_unique<Derived>(static_cast<const Derived&>(*this));
    }
};

// Base is defined, and already provides CloneableInterface
template <class Derived, class AnotherBase>
class Cloneable<Derived, AnotherBase, true> : public AnotherBase {
   ...
};

// Base is defined, but has no CloneableInterface
template <class Derived, class AnotherBase>
class Cloneable<Derived, AnotherBase, false> : public AnotherBase, public CloneableInterface {
    ...
};

Пример использования:

class Base : public Cloneable<Base> {
};

// Just some struct to test multiple inheritance
struct Other {
};

struct Derived : Cloneable<Derived, InheritFrom<Base, Other>> {
};

struct OtherBase {
};

struct OtherDerived : Cloneable<OtherDerived, InheritFrom<OtherBase>> {
};

int main() {
    // compiles and runs
    auto base_ptr = std::make_unique<Base>();
    auto derived_ptr = std::make_unique<Derived>();
    auto base_clone = base_ptr->Clone();
    auto derived_clone = derived_ptr->Clone();

    auto otherderived_ptr = std::make_unique<OtherDerived>();
    auto otherderived_clone = otherderived_ptr->Clone();
}

Любая критика и предложения по улучшению приветствуются!

...