提高克隆模式的安全性
Improving safety of Clone pattern
如果想在 C++ 中实现 Clone
模式,他可能不确定安全性,因为派生的 class 可能会忘记覆盖它:
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;
}
在这方面改进 C++ 中的 Clone
模式有哪些可能的方法?
有人问了一个更笼统的问题:
Forcing a derived class to overload a virtual method in a non-abstract base class
但是,在 C++ 中没有好的解决方案似乎太笼统了——讨论是关于强制方法覆盖的可能方法。我更感兴趣的是发现一个有用的模式,这可能在使用 Cloneable 模式的确切情况下有所帮助。
这是对其中一个答案的阐述,建议使用 typeid 进行运行时检查:
Forcing a derived class to overload a virtual method in a non-abstract base class
使用CRTP,可以得出以下基本思路:
创建 class Cloneable<Derived>
,它管理 Derived
的克隆,并添加所有需要的运行时检查(似乎即使使用 CRTP 也无法进行 compile-time 检查).
然而,这并不简单,还必须通过 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();
}
欢迎批评和改进建议!
C++17 及更新版本提供 std::any
。那么理论上,您可以创建一个 returns 一个 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;
}
同样,这是未经测试的,但理论上应该可行。
如果想在 C++ 中实现 Clone
模式,他可能不确定安全性,因为派生的 class 可能会忘记覆盖它:
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;
}
在这方面改进 C++ 中的 Clone
模式有哪些可能的方法?
有人问了一个更笼统的问题: Forcing a derived class to overload a virtual method in a non-abstract base class
但是,在 C++ 中没有好的解决方案似乎太笼统了——讨论是关于强制方法覆盖的可能方法。我更感兴趣的是发现一个有用的模式,这可能在使用 Cloneable 模式的确切情况下有所帮助。
这是对其中一个答案的阐述,建议使用 typeid 进行运行时检查: Forcing a derived class to overload a virtual method in a non-abstract base class
使用CRTP,可以得出以下基本思路:
创建 class Cloneable<Derived>
,它管理 Derived
的克隆,并添加所有需要的运行时检查(似乎即使使用 CRTP 也无法进行 compile-time 检查).
然而,这并不简单,还必须通过 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();
}
欢迎批评和改进建议!
C++17 及更新版本提供 std::any
。那么理论上,您可以创建一个 returns 一个 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;
}
同样,这是未经测试的,但理论上应该可行。