【问题标题】:"Illegal reference to non-static member" when trying to implement CRTP尝试实施 CRTP 时“非法引用非静态成员”
【发布时间】:2021-02-18 03:51:25
【问题描述】:

我正在尝试实现奇怪的重复模板模式 (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 是不是这里最好/最干净的方法?

【问题讨论】:

  • 编译器的字面意思是它不能访问,因为该成员是非静态的......所以首先你应该把它设为静态?
  • 它必须是非静态的。我将有许多子类的实例,这些实例将为此变量具有不同的值。 (并不总是 42)
  • 嗯,我明白了,那你需要static_cast&lt;Child&amp;&gt;(*this)如果有的话,请寻找副本
  • stackoverflow.com/questions/48945671/… 很接近但不一样。除了那个答案没有提到我认为大多数人使用的T&amp; underlying(){ return ... }
  • @dxiv Dynamic_cast 在这种情况下不必要地低效。

标签: c++ c++17 crtp


【解决方案1】:

这是访问 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&lt;ConcreteClass&gt;* 转换为 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();
    }
};

【讨论】:

  • "当且仅当Parent&lt;Child&gt;Child 的基数时才有可能" 正确。 "aka 别名为 Child 的类被声明为 class X : public Parent&lt;X&gt;" 但这并不遵循,除非您假设 Parent&lt;Child&gt; 用作基础对于Child 或另一个Child 派生类。但是,如果您不小心添加 class Bastard : public Parent&lt;Child&gt;,代码将顺利编译,但调用Bastard().get_value() 将导致未定义的行为。
  • @dxiv C++ 标准没有定义任何规则来防止程序员完全发疯并破坏他们自己的工作;
  • 接受这个答案,因为我觉得这是所提出问题的正确解决方案,尽管在进一步研究我的特定问题后,我认为对我来说最好的选择是使用受保护的函数int&amp; get_value() { return m_value; },每个派生类将实现。这种方法对我的特定问题更有效,因为我的子类将有自己的子类,并且试图使 CRTP 工作不止一个级别会很快变得非常难看。
  • @tjwrona1992 你可以将CTRP“嵌套”到多层,很深,函数和成员的选择将像虚函数机制一样工作。
  • 是的,但它看起来不是很丑吗?至少我能够让它工作的唯一方法看起来很糟糕。你最终得到像template &lt;typename ChildOfChild&gt; class Child : public Parent&lt;Child&lt;ChildOfChild&gt;&gt; { ... } 这样的东西我错过了什么吗?还是有更干净更好的方法来做到这一点?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-03
  • 1970-01-01
  • 1970-01-01
  • 2015-06-20
  • 2013-08-25
相关资源
最近更新 更多