微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决尝试实现 CRTP 时“非法引用非静态成员”

我正在尝试实现奇怪的重复模板模式 (CRTP) 以从父类访问子类的成员变量,但出现编译错误提示我非法引用非静态成员变量。

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

错误

非法引用非静态成员'Child::m_value'

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

CRTP 是这里最好/最干净的方法吗?

解决方法

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

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*。这提供了与虚函数相同的功能,而没有虚函数表和间接函数调用的开销。

[编辑]

简单说一下:

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

[编辑]

如果您的类层次结构变得更加复杂,函数的分派将如下所示:

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。