在模板派生类中,为什么需要使用成员函数中的"this->"限定基类成员名称?

In a templated derived class, why do I need to qualify base class member names with "this->" inside a member function?

本文关键字:成员 gt this- 基类 为什么 函数 派生      更新时间:2023-10-16

当我调查Qt的源代码时,我看到trolltech的人明确使用this关键字来访问析构函数上的字段。

inline ~QScopedPointer()
{
    T *oldD = this->d;
    Cleanup::cleanup(oldD);
    this->d = 0;
}

那么,这种用法的意义何在?有什么好处吗?

编辑:对于那些投票支持关闭这个问题的人,我怀疑这种用法适用于某些类继承情况

QScopedPointer 类定义的一部分:

template <typename T, typename Cleanup = QScopedPointerDeleter<T> >
class QScopedPointer

C++答案(一般答案(

考虑一个模板类Derived模板基类:

template <typename T>
class Base {
public:
    int d;
};
template <typename T>
class Derived : public Base<T> {
    void f () {
        this->d = 0;
    }
};

this有类型 Derived<T>,一个依赖于T的类型。所以this有一个依赖类型。因此,this->d使d成为依赖名称。从属名称在模板定义的上下文中作为非依赖名称以及在实例化上下文中查找。

如果没有this->d这个名字只会被查找为一个非依赖的名字,而不会被找到。

另一种解决方案是在模板定义本身中声明d

template <typename T>
class Derived : public Base<T> {
    using Base::d;
    void f () {
        d = 0;
    }
};

Q答案(具体答案(

dQScopedPointer的成员。它不是继承的成员。 这里不需要this->

OTOH,QScopedArrayPointer是一个模板类,d是模板基类的继承成员:

template <typename T, typename Cleanup = QScopedPointerArrayDeleter<T> >
class QScopedArrayPointer : public QScopedPointer<T, Cleanup>

所以这里this->是必要的:

inline T &operator[](int i)
{
    return this->d[i];
}

很容易看出,把this->放在任何地方更容易。

了解原因

我想并非所有用户都清楚C++为什么在非依赖基类中查找名称而不是在依赖基类中查找名称:

class Base0 {
public:
    int nd;
};
template <typename T>
class Derived2 : 
        public Base0, // non-dependent base
        public Base<T> { // dependent base
    void f () {
        nd; // Base0::b
        d; // lookup of "d" finds nothing
        f (this); // lookup of "f" finds nothing
                  // will find "f" later
    }
};

除了"标准是这样说的"之外还有一个原因:模板中名称绑定的方式的原因。

模板

可以具有在模板实例化时绑定较晚的名称:例如f in f (this) 。在Derived2::f()定义时,编译器f不知道变量、函数或类型名称。此时,f可以引用的已知实体集为空。这不是问题,因为编译器知道稍后会f查找函数名称或模板函数名称。

OTOH,编译器不知道如何处理d;它不是一个(被调用的(函数名称。无法对非(调用的(函数名称进行后期绑定。

现在,所有这些看起来都是编译时模板多态性的基本知识。真正的问题似乎是:为什么d在模板定义时不绑定到Base<T>::d

真正的问题是在模板定义时没有Base<T>::d,因为当时没有完整的类型Base<T>Base<T>被声明,但未定义!你可能会问:这个呢:

template <typename T>
class Base {
public:
    int d;
};

它看起来像一个完整类型的定义!

实际上,在实例化之前,它看起来更像:

template <typename T>
class Base;

到编译器。无法在类模板中查找名称!但仅在模板专用化(实例化(中。模板是使模板专用化的工厂,模板不是一组模板专用化。编译器可以在Base<T>中查找任何特定类型的T d,但它不能类模板中的查找d Base 。在确定类型T之前,Base<T>::d仍然是抽象Base<T>::d;只有当类型T已知时,Base<T>::d才开始引用类型 int 的变量。

这样做的结果是类模板Derived2具有完整的基类Base0但具有不完整(前向声明(的基类Base。仅对于已知的类型T,"模板类"(类模板的专用化(Derived2<T>具有完整的基类,就像任何普通类一样。

您现在看到:

template <typename T>
class Derived : public Base<T> 
实际上是一个基类规范模板(制作基类规范

的工厂(,它遵循与模板内的基类规范不同的规则。

备注:读者可能已经注意到,我在解释的末尾编造了几句话。

这是非常不同的:这里dDerived<T>中的限定名,Derived<T>是依赖的,因为T是一个模板参数。限定名称可以是后期绑定的,即使它不是(调用的(函数名称。

另一种解决方案是:

template <typename T>
class Derived : public Base<T> {
    void f () {
        Derived::d = 0; // qualified name
    }
};

这是等效的。

如果你认为在Derived<T>的定义中,有时将Derived<T>视为一个已知的完整类,而在其他时候则作为一个未知类不一致,那么你是对的。

我猜它与 Cleanup(( 例程的重载使用有关。 传递的类型由模板类型 T 显式控制,而模板类型 T 又可以控制调用哪个重载版本的 Cleanup((。