如何在不邀请未来对象切片的情况下实现ICloneable [英] How to implement ICloneable without inviting future object-slicing

查看:59
本文介绍了如何在不邀请未来对象切片的情况下实现ICloneable的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的问题是关于如何实现经典的 ICloneable 接口,以便在将来的程序员不关注时不会导致意外的对象切片.这是我想检测的一种编程错误的示例(最好是在编译时):

My question is about how to implement the classic ICloneable interface in such a way that it won't lead to inadvertent object-slicing when a future programmer isn't paying close attention. Here's an example of the kind of programming error I'd like to detect (preferably at compile-time):

#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;
}

如果 B (或 B 的任何其他非抽象子类)未定义其自身,则C ++中是否有任何方法可以说服编译器发出错误? clone()方法?简而言之,是否有任何自动方法可以在运行时检测此错误?

Is there any way in C++ to convince the compiler to emit an error if B (or any further non-abstract subclasses of B) doesn't define its own clone() method? Short of that, is there any automatic way to detect this error at run-time?

推荐答案

前一段时间,我在相同的情况下遇到了同样的问题,而没有找到令人满意的解决方案.

It's a while ago that I faced the very same issue in the very same situation without finding a satisfying solution.

再次考虑这一点,我发现可能是一个解决方案(充其量):

Thinking about this again, I found something which might be a solution (at best):

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

输出:

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

coliru上的实时演示

技巧实际上很简单:

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

Instead of overriding clone() itself, it is used as trampoline into a virtual onClone() method. Hence, clone() can check the result for correctness before returning it.

这不是编译时检查,而是运行时检查(我认为是第二好的选择).假设至少应该在开发过程中对开发中的类库的每个类进行检查/调试,我发现这是相当可靠的.

This is not a compile-time check but a run-time check (what I consider as second best option). Assuming that every class of a class library in development should hopefully be checked / debugged at least during development I find this quite reliable.

SO的公认答案:SO:C ++中的可重用成员函数向我展示了一种使它对复制更加免疫的方法/粘贴错误:

The accepted answer to SO: Reusable member function in C++ showed me a way to make this even more immune against copy/paste errors:

不是在每个覆盖的 clone()中键入类名,而是通过 decltype()获得类名:

Instead of typing out the class name in every overridden clone(), the class name is obtained via decltype():

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

在大肠杆菌上进行实时演示

这篇关于如何在不邀请未来对象切片的情况下实现ICloneable的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆