关于C++:派生奇怪的递归模板和协方差

Derived curiously recurring templates and covariance

假设我有一个克隆派生类的基类:

1
2
3
4
5
6
7
8
9
10
class Base
{
    public:
        virtual Base * clone()
        {
            return new Base();
        }

        // ...
};

我有一组派生类,这些派生类是使用奇怪的循环模板模式实现的:

1
2
3
4
5
6
7
8
9
10
11
template <class T>
class CRTP : public Base
{
    public:
        virtual T * clone()
        {
            return new T();
        }

        // ...
};

我试图从中得到进一步的结论:

1
2
3
4
5
class Derived : public CRTP<Derived>
{
    public:
        // ...
};

我得到的编译错误影响:

1
error C2555: 'CRTP<T>::clone': overriding virtual function return type differs and is not covariant from 'Base::clone'

我意识到这可能是由于编译器在实例化crtp时不完全了解派生的继承树。此外,用(base*)替换返回类型(t*)也会编译。但是,我想知道是否有一个保留了上述语义的工作。


A不是很好的解决方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Base
{
    protected:
        virtual Base * clone_p()
        {
            return new Base();
        }
};


template <class T>
class CRTP : public Base
{
    protected:
        virtual CRTP* clone_p()
        {
            return new T;
        }
    public:
        T* clone()
        {
            CRTP* res = clone_p();
            return static_cast<T*>(res);
        }
};


class Derived : public CRTP<Derived>
{
    public:
};

如果你觉得更安全的话,用dynamic_cast<>代替static


如果必须使用不同的语法来指定完整类型,则可以执行以下操作(警告:未测试代码):

我们先从机器开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// this gives the complete type which needs to be used to create objects
// and provides the implementation of clone()
template<typename T> class Cloneable:
  public T
{
public:
  template<typename... U> Cloneable(U&&... u): T(std::forward<U>(u) ...) {}
  T* clone() { return new Cloneable(*this); }
private:
  // this makes the class complete
  // Note: T:: to make it type dependent, so it can be found despite not yet defined
  typename T::CloneableBase::CloneableKey unlock() {}
};

// this provides the clone function prototype and also makes sure that only
// Cloneable<T> can be instantiated
class CloneableBase
{
  template<typename T> friend class Cloneable;

  // this type is only accessible to Clonerable instances
  struct CloneableKey {};

  // this has to be implemented to complete the class; only Cloneable instances can do that
  virtual CloneableKey unlock() = 0;
public:
  virtual CloneableBase* clone() = 0;
  virtual ~CloneableBase() {}
};

好了,现在是实际的类层次结构。这是相当标准的,没有CRTP中间体或其他并发症。但是,没有类实现clone函数,而是所有类都从CloneableBase继承声明(直接或间接)。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Base inherits clone() from CloneableBase
class Base:
  public CloneableBase
{
  // ...
};

// Derived can inherit normally from Base, nothing special here
class Derived:
  public Base
{
  // ...
};

下面是创建对象的方法:

1
2
3
4
5
6
7
8
9
10
11
12
// However, to create new instances, we actually need to use Cloneable<Derived>
Cloneable<Derived> someObject;
Derived* ptr = new Cloneable<Derived>(whatever);

// Now we clone the objects
Derived* clone1 = someObject.clone();
Derived* clone2 = ptr->clone();

// we can get rid og the objects the usual way:
delete ptr;
delete clone1;
delete clone2;

注意,Cloneable是一个Derived类(它是一个子类),因此您只需要将Cloneable用于构造,否则可以假装使用Derived对象(那么,tyepinfo也将它标识为Cloneable)。