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

use*_*ged 35 c++ qt templates this name-lookup

当我调查Qt的源代码时,我发现trolltech的人明确使用this关键字来访问析构函数中的字段.

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

那么,这个用法有什么意义呢?有什么好处吗?

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

QScopedPointer类定义的一部分:

template <typename T, typename Cleanup = QScopedPointerDeleter<T> >
class QScopedPointer
Run Code Online (Sandbox Code Playgroud)

cur*_*guy 45

C++答案(一般答案)

考虑一个Derived带有模板基类的模板类:

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

template <typename T>
class Derived : public Base<T> {
    void f () {
        this->d = 0;
    }
};
Run Code Online (Sandbox Code Playgroud)

this有类型Derived<T>,依赖的类型T.所以this有依赖类型.所以this->dd一个从属名称.在模板定义的上下文中查找从属名称作为非依赖名称并在实例化的上下文中查找.

没有this->,名称d只会被查找为非依赖名称,而不能找到.

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

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

Qanswer(具体答案)

d是...的成员QScopedPointer.它不是一个继承的成员.this->这里没有必要.

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

template <typename T, typename Cleanup = QScopedPointerArrayDeleter<T> >
class QScopedArrayPointer : public QScopedPointer<T, Cleanup>
Run Code Online (Sandbox Code Playgroud)

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

inline T &operator[](int i)
{
    return this->d[i];
}
Run Code Online (Sandbox Code Playgroud)

很容易看出,放在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
    }
};
Run Code Online (Sandbox Code Playgroud)

"标准如此说"旁边有一个原因:模板中名称绑定的方式起作用.

在模板实例化时,模板可以具有后期绑定的名称:例如,ff (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;
};
Run Code Online (Sandbox Code Playgroud)

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

实际上,直到实例化,它看起来更像是:

template <typename T>
class Base;
Run Code Online (Sandbox Code Playgroud)

到编译器.无法在类模板中查找名称!但仅限于模板特化(实例化).模板是一个工厂,使模板专业化,模板不是一组模板专业化.编译器可以查找dBase<T>任何特定类型T,但它无法查找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> 
Run Code Online (Sandbox Code Playgroud)

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

备注:读者可能已经注意到我在解释结束时编写了一些短语.

这是非常不同的:这里d是一个限定名称Derived<T>,Derived<T>因为它T是一个模板参数.即使不是(称为)函数名,限定名也可以延迟绑定.

另一个解决方案是:

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

这是等效的.

如果你认为在定义里面Derived<T>,Derived<T>作为一个已知的完整类的处理有时和作为一个未知的类在其他时间不一致,那么,你是对的.