简体   繁体   English

尝试实施 CRTP 时出现“对非静态成员的非法引用”

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

I am trying to implement the curiously recurring template pattern (CRTP) to access a member variable of a child class from the parent class, but I am getting a compilation error saying I am illegally referencing a non-static member variable.我正在尝试实施奇怪的重复模板模式 (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;
}

Error:错误:

illegal reference to non-static member 'Child::m_value'对非静态成员“Child::m_value”的非法引用

How can I properly access the member variable of the child class from within the parent class?如何从父 class 中正确访问子 class 的成员变量?

Is CRTP even the best/cleanest approach here? CRTP 是这里最好/最干净的方法吗?

Here is the correct way to access members of a CRTP derived class.这是访问 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>{};

As the concrete type ConcreteClass and its relation to Parent is known ate compile-time, a static_cast is sufficient to convert this from Parent<ConcreteClass>* to a ConcreteClass* .由于具体类型 ConcreteClass 及其与 Parent 的关系在编译时已知,因此 static_cast 足以将thisParent<ConcreteClass>*转换为ConcreteClass* This provides the same functionality as virtual functions without the overhead of a virtual function table, and indirect function calls.这提供了与虚拟函数相同的功能,而没有虚拟 function 表和间接 function 调用的开销。

[edit] [编辑]

Just to be clear:只是要清楚:

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

[edit] [编辑]

If your class hierarchy gets a bit more complex, the dispatching of functions will look like this:如果您的 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();
    }
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM