c++ - programming - sdk qt
En una clase derivada con plantilla, ¿por qué necesito calificar los nombres de los miembros de la clase base con "this->" dentro de una función miembro? (2)
Respuesta de C ++ (respuesta general)
Considere una clase de plantilla Derived
de una clase base de plantilla:
template <typename T>
class Base {
public:
int d;
};
template <typename T>
class Derived : public Base<T> {
void f () {
this->d = 0;
}
};
this
tiene el tipo Derived<T>
, un tipo que depende de T
Entonces this
tiene un tipo dependiente. Entonces this->d
hace d
un nombre dependiente. Los nombres dependientes se buscan en el contexto de la definición de la plantilla como nombres no dependientes y en el contexto de creación de instancias.
Sin this->
, el nombre d
solo se buscaría como un nombre no dependiente y no se encontraría.
Otra solución es declarar d
en la definición de la plantilla en sí:
template <typename T>
class Derived : public Base<T> {
using Base::d;
void f () {
d = 0;
}
};
Qanswer (respuesta específica)
d
es un miembro de QScopedPointer
. No es un miembro heredado. this->
no es necesario aquí.
template <typename T, typename Cleanup = QScopedPointerArrayDeleter<T> >
class QScopedArrayPointer : public QScopedPointer<T, Cleanup>
entonces this->
es necesario here :
inline T &operator[](int i)
{
return this->d[i];
}
Es fácil ver que es más fácil simplemente poner this->
todas partes.
Comprende el motivo
Supongo que no está claro para todos los usuarios de C ++ por qué los nombres se buscan en clases base no dependientes pero no en clases base dependientes:
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
}
};
Hay una razón además de "el estándar lo dice": la causa del enlace del nombre de la vía en las plantillas funciona.
Las plantillas pueden tener un nombre que se vincula tarde, cuando se crea una instancia de la plantilla: por ejemplo, f
en f (this)
. En el punto de la Derived2::f()
de Derived2::f()
, no hay ninguna variable, función o nombre de tipo f
conocido por el compilador. El conjunto de entidades conocidas a las que f
podría referirse está vacío en este punto. Esto no es un problema porque el compilador sabe que se buscará más tarde como un nombre de función, o un nombre de función de plantilla.
OTOH, el compilador no sabe qué hacer con d
; no es un nombre de función (llamado). No hay forma de hacer vinculaciones tardías en nombres de funciones no llamadas (llamadas).
Ahora, todo esto puede parecer conocimiento elemental del polimorfismo de plantilla en tiempo de compilación. La verdadera pregunta parece ser: ¿por qué no está unido a Base<T>::d
en el tiempo de definición de la plantilla?
El problema real es que no hay Base<T>::d
en el tiempo de definición de plantilla, porque no hay un tipo completo Base<T>
en ese momento: Base<T>
está declarado, pero no definido. Puede preguntar: ¿qué tal esto?
template <typename T>
class Base {
public:
int d;
};
¡parece la definición de un tipo completo!
En realidad, hasta la instanciación, se parece más a:
template <typename T>
class Base;
al compilador ¡No se puede buscar un nombre en una plantilla de clase! Pero solo en una especialización de plantilla (instanciación). La plantilla es una fábrica para hacer especialización de plantilla, una plantilla no es un conjunto de especialización de plantilla . El compilador puede buscar d
en Base<T>
para cualquier tipo particular T
, pero no puede buscar d
en la plantilla de clase Base
. Hasta que se determine un tipo T
, Base<T>::d
sigue siendo la Base<T>::d
abstracta Base<T>::d
; solo cuando se conoce el tipo T
, Base<T>::d
comienza a referirse a una variable de tipo int
.
La consecuencia de esto es que la plantilla de clase Derived2
tiene una clase base completa Base0
pero una clase base incompleta (declarada adelante) Base
. Solo para un tipo conocido T
, la "clase de plantilla" (especializaciones de una plantilla de clase) Derived2<T>
tiene una clase base completa, como cualquier clase normal.
Ahora ves eso:
template <typename T>
class Derived : public Base<T>
es en realidad una plantilla de especificación de clase base (una fábrica para hacer especificaciones de clase base) que sigue diferentes reglas de una especificación de clase base dentro de una plantilla.
Observación: El lector puede haber notado que he inventado algunas frases al final de la explicación.
Esto es muy diferente: aquí d
es un nombre calificado en Derived<T>
, y Derived<T>
es dependiente ya que T
es un parámetro de plantilla. Un nombre calificado puede vincularse tarde incluso si no es un nombre de función (llamado).
Otra solución más es:
template <typename T>
class Derived : public Base<T> {
void f () {
Derived::d = 0; // qualified name
}
};
Esto es equivalente
Si crees que dentro de la definición de Derived<T>
, el tratamiento de Derived<T>
como una clase completa conocida a veces y como una clase desconocida algunas otras veces en inconsistente, bien, tienes razón.
Mientras investigo el código fuente de Qt, vi que los chicos de trolltech usan explícitamente this
palabra clave para acceder a un campo en destructor.
inline ~QScopedPointer()
{
T *oldD = this->d;
Cleanup::cleanup(oldD);
this->d = 0;
}
Entonces, ¿de qué sirve este uso? ¿Hay algún beneficio?
Editar: para aquellos que voten por cerrar esta pregunta, sospecho que este uso es para algunos casos de herencia de clase
Una parte de la definición de la clase QScopedPointer :
template <typename T, typename Cleanup = QScopedPointerDeleter<T> >
class QScopedPointer
Supongo que se trata de un uso sobrecargado de la rutina Cleanup (). El tipo que se pasa está explícitamente controlado por el tipo de plantilla T, que a su vez puede controlar qué versión sobrecargada de Cleanup () se invoca.