问题描述
我正在尝试实现奇怪的重复模板模式 (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 足以将 this
从 Parent<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();
}
};