"Illegal reference to non-static member" 尝试实施 CRTP 时

"Illegal reference to non-static member" when trying to implement CRTP

我正在尝试实现奇怪的循环模板模式 (CRTP) 以从父 class 访问子 class 的成员变量,但我收到一个编译错误,说我是非法引用非静态成员变量。

#include <iostream>

template <typename Child>
class Parent
{
public:
    int get_value()
    {
        return Child::m_value;
    }

    virtual ~Parent() = default;
};

class Child : public Parent<Child>
{
    int m_value = 42;

    friend class Parent<Child>;
};

int main()
{
    Child child;
    std::cout << child.get_value() << std::endl;
}

错误:

illegal reference to non-static member 'Child::m_value'

如何从父 class 中正确访问子 class 的成员变量?

CRTP 甚至是 best/cleanest 方法吗?

这是访问派生的 CRTP 成员的正确方法 class。

template <typename Child>
class Parent
{
  public:
    int get_value()
    {
        // Do NOT use dynamic_cast<> here.
        return static_cast<Child*>(this)->m_value;
    }

    ~Parent() { /*...*/ }; // Note: a virtual destructor is not necessary,
                           // in any case, this is not the place to
                           // define it.
};

// A virtual destructor is not needed, unless you are planning to derive 
// from ConcreteClass.

class ConcreteClass : public Parent<ConcreteClass> 
{
    friend class Parent<ConcreteClass>;  // Needed if Parent needs access to 
                                         // private members of ConcreteClass

    // If you plan to derive from ConcreteClass, this is where you need to declare
    // the destructor as virtual.  There is no ambiguity as to the base of
    // ConcreteClass, so the static destructor of Parent<ConcreteClass> will
    // always be called by the compiler when destoying a ConcreteClass object. 
    //
    // Again: a virtual destructor at this stage is optional, and depends on 
    // your future plans for ConcreteClass.
  public:
    virtual ~ConcreteClass() {};

  private:
    int m_value;
}; 

// only ConcreteClass needs (optionally) a virtual destructor, and
// that's because your application will deal with ConcretClass objects
// and pointers, for example, the class below is totally unrelated to 
// ConcreteClass, and no type-safe casting between the two is possible.

class SomeOtherClass : Parent<SomeOtherClass> { /* ... */ }

ConcreteClass obj1;
// The assignment below is no good, and leads to UB.
SomeOtherClass* p = reinterpret_cast<ConcreteClass*>(&obj1); 

// This is also not possible, because the static_cast from
// Parent<UnrelatedClass>* to UnrelatedClass* will not compile.
// So, to keep your sanity, your application should never  
// declare pointers to Parent<T>, hence there is never any 
// need for a virtual destructor in Parent<> 

class UnrelatedClass {/* ... */ };

auto obj2 = Parent<UnrelatedClass>{};

由于具体类型 ConcreteClass 及其与 Parent 的关系在编译时已知,static_cast 足以将 thisParent<ConcreteClass>* 转换为 ConcreteClass* .这提供了与虚拟函数相同的功能,而没有虚拟函数的开销 table 和间接函数调用。

[编辑]

明确一点:

template <typename Child>
class Parent
{
  public:
    int get_value()
    {
        // the static cast below can compile if and only if
        // Child and Parent<Child> are related.  In the current 
        // scope, that's possible if and only if Parent<Child>
        // is a base of Child, aka that the class aliased by Child
        // was declared as:
        //   class X : public Parent<X> {};
        //   
        // Note that it is important that the relation is declared 
        // as public, or static_cast<Child*>(this) will not compile.
        //
        // The static_cast<> will work correctly, even in the case of 
        // multiple inheritance. example:
        //
        //   class A {];
        //   class B {};
        //   class C : public A
        //           , public Parent<C> 
        //           , B  
        // {
        //     friend  class Parent<C>;
        //     int m_value;
        // }; 
        //
        // Will compile and run just fine.

        return static_cast<Child*>(this)->m_value;
    }
};

[编辑]

如果您的 class 层次结构变得有点复杂,函数的调度将如下所示:

template <typename T>
class A
{
public:
  int get_value()
  {
      return static_cast<T*>(this)->get_value_impl(); 
  }

  int get_area()
  {
      return static_cast<T*>(this)->get_area_impl(); 
  }
};

template <typename T>
class B : public A<T>
{
    friend A<T>;
protected:
    int get_value_impl()
    {
        return value_;
    }
    
    int get_area_impl()
    {
        return value_ * value_;
    }

private:
   int value_; 
};

template <typename T>
class C : public B<T>
{
    // you must declare all bases in the hierarchy as friends.
    friend A<T>;
    friend B<T>;
protected:
    // here, a call to C<T>::get_value_impl()
    // will effetively call B<T>::get_value_impl(), 
    // as per usual rules.
  
    // if you need to call functions from B, use the usual 
    // syntax 
    
    int get_area_impl()
    {
        return 2 * B<T>::get_value_impl();
    }
};