首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何在不邀请未来对象切片的情况下实现ICloneable

如何在不邀请未来对象切片的情况下实现ICloneable
EN

Stack Overflow用户
提问于 2019-08-08 04:51:04
回答 2查看 219关注 0票数 2

我的问题是如何实现经典的ICloneable接口,使其不会在未来的程序员没有密切注意的情况下导致无意中的对象切片。下面是我想要检测的那种编程错误的示例(最好是在编译时):

代码语言:javascript
运行
复制
#include <stdio.h>

class ICloneable
{
public:
   virtual ICloneable * clone() const = 0;
};

class A : public ICloneable
{
public:
   A() {}
   A(const A & rhs) {}

   virtual ICloneable * clone() const {return new A(*this);}
};

class B : public A
{
public:
   B() {}
   B(const B & rhs) {}

   // Problem, B's programmer forget to add a clone() method here!
};

int main(int, char**)
{
   B b;
   ICloneable * clone = b.clone();  // d'oh!  (clone) points to an A, not a B!
   return 0;
}

如果C++ (或者其他非抽象的B子类)没有定义自己的clone()方法,那么在B中有什么方法可以说服编译器发出错误吗?除此之外,在运行时是否有任何自动检测此错误的方法?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-08-08 06:03:25

不久前,我在同样的情况下面临着同样的问题,却没有找到令人满意的解决方案。

再一次思考这个问题,我找到了一些可能是解决方案的方法(充其量):

代码语言:javascript
运行
复制
#include <iostream>
#include <typeinfo>
#include <typeindex>

class Base { // abstract
  protected:
    Base() = default;
    Base(const Base&) = default;
    Base& operator=(const Base&) = default;
  public:
    virtual ~Base() = default;
    
    Base* clone() const
    {
      Base *pClone = this->onClone();
      const std::type_info &tiClone = typeid(*pClone);
      const std::type_info &tiThis = typeid(*this);
#if 0 // in production
      assert(std::type_index(tiClone) == type_index(tiThis)
        && "Missing overload of onClone()!");
#else // for demo
      if (std::type_index(tiClone) != std::type_index(tiThis)) {
        std::cout << "ERROR: Missing overload of onClone()!\n"
          << "  in " << tiThis.name() << '\n';
      }
#endif // 0
      return pClone;
    }
    
  protected:
    virtual Base* onClone() const = 0;
};

class Instanceable: public Base {
  public:
    Instanceable() = default;
    Instanceable(const Instanceable&) = default;
    Instanceable& operator=(const Instanceable&) = default;
    virtual ~Instanceable() = default;
    
  protected:
    virtual Base* onClone() const { return new Instanceable(*this); }
};

class Derived: public Instanceable {
  public:
    Derived() = default;
    Derived(const Derived&) = default;
    Derived& operator=(const Derived&) = default;
    virtual ~Derived() = default;
    
  protected:
    virtual Base* onClone() const override { return new Derived(*this); }
};

class WrongDerived: public Derived {
  public:
    WrongDerived() = default;
    WrongDerived(const WrongDerived&) = default;
    WrongDerived& operator=(const WrongDerived&) = default;
    virtual ~WrongDerived() = default;

  // override missing
};

class BadDerived: public Derived {
  public:
    BadDerived() = default;
    BadDerived(const BadDerived&) = default;
    BadDerived& operator=(const BadDerived&) = default;
    virtual ~BadDerived() = default;

  // copy/paste error
  protected:
    virtual Base* onClone() const override { return new Derived(*this); }
};

#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ 

int main()
{
  DEBUG(Instanceable obj1);
  DEBUG(Base *pObj1Clone = obj1.clone());
  DEBUG(std::cout << "-> " << typeid(*pObj1Clone).name() << "\n\n");
  DEBUG(Derived obj2);
  DEBUG(Base *pObj2Clone = obj2.clone());
  DEBUG(std::cout << "-> " << typeid(*pObj2Clone).name() << "\n\n");
  DEBUG(WrongDerived obj3);
  DEBUG(Base *pObj3Clone = obj3.clone());
  DEBUG(std::cout << "-> " << typeid(*pObj3Clone).name() << "\n\n");
  DEBUG(BadDerived obj4);
  DEBUG(Base *pObj4Clone = obj4.clone());
  DEBUG(std::cout << "-> " << typeid(*pObj4Clone).name() << "\n\n");
}

输出:

代码语言:javascript
运行
复制
Instanceable obj1;
Base *pObj1Clone = obj1.clone();
std::cout << "-> " << typeid(*pObj1Clone).name() << '\n';
-> 12Instanceable

Derived obj2;
Base *pObj2Clone = obj2.clone();
std::cout << "-> " << typeid(*pObj2Clone).name() << '\n';
-> 7Derived

WrongDerived obj3;
Base *pObj3Clone = obj3.clone();
ERROR: Missing overload of onClone()!
  in 12WrongDerived
std::cout << "-> " << typeid(*pObj3Clone).name() << '\n';
-> 7Derived

BadDerived obj4;
Base *pObj4Clone = obj4.clone();
ERROR: Missing overload of onClone()!
  in 10BadDerived
std::cout << "-> " << typeid(*pObj4Clone).name() << '\n';
-> 7Derived

Live Demo on coliru

诀窍其实很简单:

它不是覆盖clone()本身,而是作为蹦床使用到virtual onClone()方法中。因此,clone()可以在返回结果之前检查结果的正确性。

这不是编译时检查,而是运行时检查(我认为这是次优选项)。假设开发中的类库的每个类都应该被检查/调试,至少在开发期间是这样的,我认为这是非常可靠的。

SO: Reusable member function in C++的公认答案向我展示了一种使其更不受复制/粘贴错误影响的方法:

不是在每个重写的clone()中键入类名,而是通过decltype()获得类名。

代码语言:javascript
运行
复制
class Instanceable: public Base {
  public:
    Instanceable() = default;
    Instanceable(const Instanceable&) = default;
    Instanceable& operator=(const Instanceable&) = default;
    virtual ~Instanceable() = default;
    
  protected:
    virtual Base* onClone() const
    {
      return new std::remove_const_t<std::remove_pointer_t<decltype(this)>>(*this);
    }
};

class Derived: public Instanceable {
  public:
    Derived() = default;
    Derived(const Derived&) = default;
    Derived& operator=(const Derived&) = default;
    virtual ~Derived() = default;
    
  protected:
    virtual Base* onClone() const override
    {
      return new std::remove_const_t<std::remove_pointer_t<decltype(this)>>(*this);
    }
};

Live Demo on coliru

票数 1
EN

Stack Overflow用户

发布于 2019-08-08 06:42:11

不要让A和B继承IClonable。使用包装器(BluePrint)代替:

代码语言:javascript
运行
复制
struct IClonable {
    virtual ~IClonable() = default;
    virtual IClonable * clone() const = 0;
};

template<typename T>
class BluePrint final : IClonable {
public:
    explicit BluePrint(T * element) : element(element) {
    }
    IClonable * clone() const override {
        T * copy = element->clone();
        return new BluePrint(copy);
    }
    T * get() const {
        return element;
    }

private:
    T * const element;
};

struct A {
    A * clone() const;
};

struct B : A {
    B * clone() const;
};

但是,您必须稍微修改代码,因为这将返回包装器的一个克隆,而不是立即返回要克隆的元素的克隆。再说一遍,我不知道您打算如何使用IClonable接口,所以我无法为您完成这个示例。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57405375

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档