В шаблоном производном классе, почему мне нужно квалифицировать имена членов базового класса с помощью "this->" внутри функции-члена?

Пока я изучаю исходный код 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

Ответ 1

Ответ на С++ (общий ответ)

Рассмотрим шаблонный класс 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;
    }
};

Qanswer (конкретный ответ)

d является членом QScopedPointer. Это не унаследованный член. 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-> всюду.

Понять причину

Я думаю, всем пользователям на С++ не ясно, почему имена ищутся в независящих базовых классах, но не в зависимых базовых классах:

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 в 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;

для компилятора. Имя не может быть просмотрено в шаблоне класса! Но только в шаблонной специализации (экземпляре). Шаблон представляет собой factory, чтобы сделать специализацию шаблона, шаблон не является набором специализированных шаблонов. Компилятор может искать d в Base<T> для любого конкретного типа T, но он не может lookup 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> 

на самом деле является базовым классом спецификации (a factory для спецификации базового класса), который следует за разными правилами из спецификации базового класса внутри шаблона.

Примечание: Возможно, читатель заметил, что в конце объяснения я составил несколько фраз.

Это совсем другое: здесь d является квалифицированным именем в Derived<T>, а Derived<T> зависит от T является параметром шаблона. Квалифицированное имя может быть связано с поздним сроком, даже если оно не является именем функции (называемой).

Еще одно решение:

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

Это эквивалентно.

Если вы считаете, что внутри определения Derived<T> обработка Derived<T> как известного полного класса иногда и как неизвестный класс несколько раз непоследовательна, ну, вы правы.

Ответ 2

Я предполагаю, что это относится к перегруженному использованию процедуры Cleanup(). Передаваемый тип явно контролируется типом шаблона T, который, в свою очередь, может управлять выгрузкой перегруженной версии Cleanup().