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

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

Ответы [ 2 ]

44 голосов
/ 27 октября 2011

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

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-> везде.

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

Я полагаю, что не всем пользователям 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 в 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;

компилятору. Имя не может быть найдено в шаблоне класса! Но только в шаблонной специализации. Шаблон - это фабрика для создания шаблона специализации, шаблон не является набором шаблона специализации . Компилятор может искать d в Base<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> 

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

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

Это очень отличается: здесь 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> как известного завершенного класса иногда и как неизвестного класса иногда противоречива, ну, вы правы.

1 голос
/ 27 октября 2011

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

...