在模板派生类中,为啥我需要在成员函数中使用“this->”来限定基类成员名称?

Posted

技术标签:

【中文标题】在模板派生类中,为啥我需要在成员函数中使用“this->”来限定基类成员名称?【英文标题】:In a templated derived class, why do I need to qualify base class member names with "this->" inside a member function?在模板派生类中,为什么我需要在成员函数中使用“this->”来限定基类成员名称? 【发布时间】:2011-10-26 20:07:31 【问题描述】:

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

inline ~QScopedPointer()

    T *oldD = this->d;
    Cleanup::cleanup(oldD);
    this->d = 0;

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

编辑:对于那些投票结束这个问题的人,我怀疑这种用法是针对某些类继承情况

QScopedPointer class定义的一部分:

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

【问题讨论】:

【参考方案1】:

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&lt;T&gt;,该类型依赖于T。所以this 有一个依赖类型。所以this-&gt;d 使d 成为从属名称。依赖名称在模板定义的上下文中作为非依赖名称和在实例化的上下文中查找。

如果没有this-&gt;,名称d 只会作为非依赖名称进行查找,而不会被找到。

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

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

Qanswer(具体答案)

d 是 member of QScopedPointer。它不是继承的成员。 this-&gt; 这里不需要。

OTOH,QScopedArrayPointer is a template class and d is an inherited member of a template base class:

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

所以this-&gt; 是必要的here:

inline T &operator[](int i)

    return this->d[i];

很容易看出,将this-&gt; 放在任何地方更容易。

了解原因

我想所有 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 (this) 中的f。在Derived2::f() 定义处,编译器不知道变量、函数或类型名称ff 可以引用的已知实体集此时为空。这不是问题,因为编译器知道它稍后会查找 f 作为函数名或模板函数名。

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

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

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

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

看起来像是完整类型的定义!

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

template <typename T>
class Base;

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

这样做的结果是类模板 Derived2 有一个完整的基类Base0,但有一个不完整的(前向声明的)基类Base。仅对于已知类型T,“模板类”(类模板的特化)Derived2&lt;T&gt; 具有完整的基类,就像任何普通类一样。

你现在看到了:

template <typename T>
class Derived : public Base<T> 

实际上是一个基类规范模板(制作基类规范的工厂),它遵循与模板内的基类规范不同的规则。

备注: 读者可能已经注意到我在解释的最后编造了一些短语。

这是非常不同的:这里dDerived&lt;T&gt; 中的限定名称,而Derived&lt;T&gt; 是依赖的,因为T 是模板参数。限定名称可以是后期绑定的,即使它不是(调用的)函数名称。

另一个解决方案是:

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

这是等价的。

如果您认为在 Derived&lt;T&gt; 的定义中,有时将 Derived&lt;T&gt; 视为已知的完整类,而有时将其视为未知类不一致,那么您是对的。

【讨论】:

版主说明 此答案下的评论已被清除,因为它们退化为纯噪音。请使用Stack Overflow Chat 进行扩展讨论并记住保持文明。【参考方案2】:

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

【讨论】:

以上是关于在模板派生类中,为啥我需要在成员函数中使用“this->”来限定基类成员名称?的主要内容,如果未能解决你的问题,请参考以下文章

在派生类中专门化模板成员

如何在派生类中初始化需要构造函数参数的成员对象?

在派生类中使用模板基类模板构造函数

为啥我不能访问静态多态派生类中的受保护成员?

为啥在元类派生自ABCMeta的类中需要使用@abstractmethod?

为啥 C++ 构造函数在继承中需要默认参数?