Funciones miembro no estáticas
Una función miembro no estática es una función que se declara en una especificación de miembro de una clase sin un especificador static o friend. (para el efecto de esas palabras claves véase funciones miembro estáticas y declaración friend).
class S { int mf1(); // declaración de función miembro no estática void mf2() volatile, mf3() &&; // puede ser calificada cv y/o calificada con referencia // la declaración anterior es equivalente a dos declaraciones separadas: // void mf2() volatile; // void mf3() &&; int mf4() const { return data; } // puede ser definida en línea (inline) virtual void mf5() final; // puede ser virtual, puede usar final/override S() : data(12) {} // los constructores también son funciones miembro int data; }; int S::mf1() { return 7; } // no está definida en línea, tiene que definirse // en el espacio de nombres
Los constructores, destructores, y funciones de conversión usan una sintaxis especial para sus declaraciones. Las reglas descritas en esta página pueden no aplicarse a estas funciones. Vea sus respectivas páginas para más detalles.
Contenido |
[editar] Explicación
Se permite cualquier declaración de función, con elementos de sintaxis adicionales que solo están disponibles para funciones miembro no estáticas: especificadores puros, calificadores cv, especificadores final y override, calificadores de referencia (desde C++11) y listas de inicialización de miembros.
Se puede llamar a una función miembro no estática de clase X:
Llamar a una función miembro no estática de la clase X
en un objeto de que no es de tipo X
, o un tipo derivado de X
, crea un comportamiento indefinido.
Dentro del cuerpo de una función miembro no estática de X
, cualquier expresión-id E (por ejemplo, un identificador) que se resuelve en un miembro no estático no tipo X
, o de una clase base de X
, se transforma en una expresión de acceso de miembro (*this).E (a menos que ya sea parte de una expresión de acceso de miembro). Esto no ocurre en un contexto de definición de plantilla, por lo que un nombre debe tener el prefijothis-> explícitamente para convertirse en un nombre dependiente.
struct S { int n; void f(); }; void S::f() { n = 1; // transformado a (*this).n = 1; } int main() { S s1, s2; s1.f(); // cambia s1.n }
Dentro del cuerpo de una función miembro no estática de X
, cualquier identificador no calificado que se resuelva en un miembro estático, un enumerador o un tipo anidado de X
o de una clase base de X
, se transforma en el identificador calificado correspondiente.
struct S { static int n; void f(); }; void S::f() { n = 1; // transformado a S::n = 1; } int main() { S s1, s2; s1.f(); // cambia S::n }
[editar] Funciones miembro calificadas const y volatile
Una función miembro no estática se puede declarar con una secuencia de calificadores cv (const, volatile o una combinación de const y volatile), esta secuencia aparece después de la lista de parámetros en la declaración de función. Las funciones con diferentes secuencias de calificadores cv (o sin secuencia) tienen diferentes tipos y, por lo tanto, pueden sobrecargarse entre sí.
En el cuerpo de una función con una secuencia de calificadores cv, el puntero *this está calificado cv, por ejemplo, en una función miembro con cualificador const
, normalmente solo se pueden llamar a otras funciones miembro con cualificador const
(todavía se puede llamar a una función miembro no constante si se aplica const_cast o mediante una ruta de acceso que no involucre a this).
#include <vector> struct Array { std::vector<int> data; Array(int sz) : data(sz) {} // función miembro const int operator[](int idx) const { // el puntero this tiene tipo const Array* return data[idx]; // transformado a (*this).data[idx]; } // función miembro no constante int& operator[](int idx) { // el puntero this tiene tipo Array* return data[idx]; // transformado a (*this).data[idx] } }; int main() { Array a(10); a[1] = 1; // de acuerdo: el tipo de a[1] es int& const Array ca(10); ca[1] = 2; // ERROR: el tipo de ca[1] es int }
Funciones miembro calificadas con referenciaSe puede declarar una función miembro no estática sin calificador de referencia, con un calificador de referencia lvalue (el símbolo
Nota: a diferencia de la calificación-cv, la calificación de referencia no cambia las propiedades del puntero this: dentro de una función calificada con referencia rvalue, *this sigue siendo una expresión lvalue. |
(desde C++11) |
[editar] Funciones virtuales y virtuales puras
Una función miembro no estática puede declararse virtual o virtual pura. Véase funciones virtuales y clases abstractas para más detalles.
Parámetros objeto explícitosSe puede declarar una función miembro no estática para que tome como su primer parámetro un parámetro objeto explícito, indicado con el prefijo clave this. struct X { void foo(this X const& self, int i); // igual a void foo(int i) const &; // void foo(int i) const &; // Error: ya declarada void bar(this X self, int i); // pasa objeto por valor: hace una copia de `*this` }; Para funciones miembro plantilla, el parámetro objeto explícito permite la deducción del tipo y categoría de valor, esta característica del lenguaje se llama "deduciendo `this`" struct X { template<typename Self> void foo(this Self&&, int); }; struct D : X {}; void ex(X& x, D& d) { x.foo(1); // Self = X& move(x).foo(2); // Self = X d.foo(3); // Self = D& } Esto hace posible deduplicar las funciones miembro constantes y no constantes, vea operador de índice de array para un ejemplo.
// un rasgo CRTP struct anadir_incremento_postfijo { template <typename Self> auto operator++(this Self&& self, int) { auto tmp = self; // Self se deduce a "algun_tipo" ++self; return tmp; } }; struct algun_tipo : anadir_incremento_postfijo { algun_tipo & operator++() { ... } }; Dentro del cuerpo de una función con un parámetro objeto explícito, no se puede usar el puntero this: todos los acceso a miembro se deben hacer a través del primer parámetro, como en las funciones miembro estáticas: struct C { void bar(); void foo(this C c) { auto x = this; // error: no this bar(); // error: no this-> implícito c.bar(); // correcto } }; Un puntero a un función miembro con un parámetro objeto explícito es un puntero a función ordinario, no un puntero a miembro: struct Y { int f(int, int) const&; int g(this Y const&, int, int); }; auto pf = &Y::f; pf(y, 1, 2); // error: los punteros a funciones miembros no son invocables (y.*pf)(1, 2); // correcto std::invoke(pf, y, 1, 2); // correcto auto pg = &Y::g; pg(y, 3, 4); // correcto (y.*pg)(3, 4); // error: pg no es un puntero a una función miembro std::invoke(pg, y, 3, 4); // correcto
|
(desde C++23) |
[editar] Funciones miembro especiales
Los constructores y destructores son funciones miembro no estáticas que utilizan una sintaxis especial para sus declaraciones (véanse sus páginas para más detalles).
Algunas funciones miembro son especiales: bajo ciertas circunstancias se definen por el compilador aún si no se definen por el usuario. Son:
(desde C++11) |
(desde C++11) |
- Destructor (hasta C++20)Destructor potencial (desde C++20)
Las funciones miembro especiales junto con los operadores de comparación (desde C++20) son las únicas funciones que pueden ser por defecto, es decir, definidas usando = default en lugar del cuerpo de función (véanse sus páginas para más detalles).
[editar] Ejemplo
#include <iostream> #include <string> #include <utility> #include <exception> struct S { int data; // constructor de conversión simple (declaración) S(int val); // constructor explícito simple (declaración) explicit S(std::string str); // función miembro const (definición) virtual int getData() const { return data; } }; // definición del constructor S::S(int val) : data(val) { std::cout << "primer constructor llamado, data = " << data << '\n'; } // este constructor tiene una cláusula catch S::S(std::string str) try : data(std::stoi(str)) { std::cout << "segundo constructor llamado, data = " << data << '\n'; } catch(const std::exception&) { std::cout << "segundo constructor ha fallado, cadena era '" << str << "'\n"; throw; // la cláusula catch en un constructor siempre debe de volver a lanzar } struct D : S { int data2; // constructor con un argumento por defecto D(int v1, int v2 = 11) : S(v1), data2(v2) {} // función miembro virtual int getData() const override { return data*data2; } // operador de asignación de solamente lvalue D& operator=(D other) & { std::swap(other.data, data); std::swap(other.data2, data2); return *this; } }; int main() { D d1 = 1; S s2("2"); try { S s3("no es un numero"); } catch(const std::exception&) {} std::cout << s2.getData() << '\n'; D d2(3, 4); d2 = d1; // de acuerdo: asignación a lvalue // D(5) = d1; // ERROR: no hay sobrecarga adecuada del operador: operator= }
Salida:
primer constructor llamado, data = 1 segundo constructor llamado, data = 2 segundo constructor ha fallado, cadena era 'no es un numero' 2 primer constructor llamado, data = 3
[editar] Informe de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
---|---|---|---|
CWG 194 | C++98 | ambiguo si una función miembro no estática podía tener el mismo nombre que el nombre de la clase que la contiene |
agregada restricción de nombre explícito |