Buscar..


Sintaxis

  • // Llamando:

    • variable.member_function ();
    • variable_pointer-> miembro_función ();
  • // Definición:

    • ret_type class_name :: member_function () cv-qualifiers {
      • cuerpo;
    • }
  • // Prototipo:

    • clase nombre_clase {
      • virt-specifier ret_type member_function () cv-qualifiers virt-specifier-seq;
      • // virt-specifier: "virtual", si corresponde.
      • // calificadores cv: "const" y / o "volátil", si corresponde.
      • // virt-specifier-seq: "anular" y / o "final", si corresponde.
    • }

Observaciones

Una función miembro no static es una función miembro class / struct / union , que se llama en una instancia particular y opera en dicha instancia. A diferencia de las funciones miembro static , no se puede llamar sin especificar una instancia.

Para obtener información sobre clases, estructuras y uniones, consulte el tema principal .

Funciones miembro no estáticas

Una class o struct puede tener funciones miembro así como variables miembro. Estas funciones tienen una sintaxis mayoritariamente similar a las funciones independientes, y pueden definirse dentro o fuera de la definición de clase; Si se define fuera de la definición de la clase, el nombre de la función tiene como prefijo el nombre de la clase y el operador de alcance ( :: :).

class CL {
  public:
    void  definedInside() {}
    void definedOutside();
};
void CL::definedOutside() {}

Estas funciones se invocan en una instancia (o referencia a una instancia) de la clase con el operador de punto ( . ), O un puntero a una instancia con el operador de flecha ( -> ), y cada llamada está vinculada a la instancia de la función fue llamado en cuando se llama a una función miembro en una instancia, tiene acceso a todos los campos de esa instancia (a través de this puntero ), pero solo puede acceder a los campos de otras instancias si esas instancias se suministran como parámetros.

struct ST {
    ST(const std::string& ss = "Wolf", int ii = 359) : s(ss), i(ii) { }

    int get_i() const { return i; }
    bool compare_i(const ST& other) const { return (i == other.i); }

  private:
    std::string s;
    int i;
};
ST st1;
ST st2("Species", 8472);

int  i = st1.get_i(); // Can access st1.i, but not st2.i.
bool b = st1.compare_i(st2); // Can access st1 & st2.

Estas funciones pueden acceder a las variables miembro y / u otras funciones miembro, independientemente de la variable o los modificadores de acceso de la función. También se pueden escribir fuera de orden, accediendo a las variables de miembro y / o llamando a las funciones de miembro declaradas antes de ellas, ya que se debe analizar toda la definición de la clase antes de que el compilador pueda comenzar a compilar una clase.

class Access {
  public:
    Access(int i_ = 8088, int j_ = 8086, int k_ = 6502) : i(i_), j(j_), k(k_) {}

    int i;
    int get_k() const { return k; }
    bool private_no_more() const { return i_be_private(); }
  protected:
    int j;
    int get_i() const { return i; }
  private:
    int k;
    int get_j() const { return j; }
    bool i_be_private() const { return ((i > j) && (k < j)); }
};

Encapsulacion

Un uso común de las funciones miembro es para la encapsulación, usando un elemento de acceso (comúnmente conocido como captador) y un mutador (comúnmente conocido como configurador) en lugar de acceder a los campos directamente.

class Encapsulator {
    int encapsulated;

  public:
    int  get_encapsulated() const { return encapsulated; }
    void set_encapsulated(int e)  { encapsulated = e; }

    void some_func() {
        do_something_with(encapsulated);
    }
};

Dentro de la clase, se puede acceder libremente a encapsulated mediante cualquier función miembro no estática; fuera de la clase, el acceso a él está regulado por las funciones miembro, usando get_encapsulated() para leerlo y set_encapsulated() para modificarlo. Esto evita modificaciones no intencionales a la variable, ya que se usan funciones separadas para leerla y escribirla. [Hay muchas discusiones sobre si los captadores y los definidores proporcionan o rompen la encapsulación, con buenos argumentos para ambas afirmaciones; este acalorado debate está fuera del alcance de este ejemplo.]

Nombre ocultar e importar

Cuando una clase base proporciona un conjunto de funciones sobrecargadas, y una clase derivada agrega otra sobrecarga al conjunto, esto oculta todas las sobrecargas proporcionadas por la clase base.

struct HiddenBase {
    void f(int) { std::cout << "int" << std::endl; }
    void f(bool) { std::cout << "bool" << std::endl; }
    void f(std::string) { std::cout << "std::string" << std::endl; }
};

struct HidingDerived : HiddenBase {
    void f(float) { std::cout << "float" << std::endl; }
};

// ...

HiddenBase hb;
HidingDerived hd;
std::string s;

hb.f(1);    // Output:  int
hb.f(true); // Output:  bool
hb.f(s);    // Output:  std::string;

hd.f(1.f);  // Output:  float
hd.f(3);    // Output:  float
hd.f(true); // Output:  float
hd.f(s);    // Error: Can't convert from std::string to float.

Esto se debe a las reglas de resolución de nombres: durante la búsqueda de nombres, una vez que se encuentra el nombre correcto, dejamos de buscar, incluso si claramente no hemos encontrado la versión correcta de la entidad con ese nombre (como en hd.f(s) ); debido a esto, la sobrecarga de la función en la clase derivada evita que la búsqueda de nombres descubra las sobrecargas en la clase base. Para evitar esto, se puede usar una declaración de uso para "importar" nombres de la clase base a la clase derivada, de modo que estén disponibles durante la búsqueda de nombres.

struct HidingDerived : HiddenBase {
     // All members named HiddenBase::f shall be considered members of HidingDerived for lookup.
    using HiddenBase::f;

    void f(float) { std::cout << "float" << std::endl; }
};

// ...

HidingDerived hd;

hd.f(1.f);  // Output:  float
hd.f(3);    // Output:  int
hd.f(true); // Output:  bool
hd.f(s);    // Output:  std::string

Si una clase derivada importa nombres con una declaración de uso, pero también declara funciones con la misma firma que las funciones en la clase base, las funciones de la clase base serán anuladas u ocultadas silenciosamente.

struct NamesHidden {
    virtual void hide_me()      {}
    virtual void hide_me(float) {}
    void hide_me(int)           {}
    void hide_me(bool)          {}
};

struct NameHider : NamesHidden {
    using NamesHidden::hide_me;

    void hide_me()    {} // Overrides NamesHidden::hide_me().
    void hide_me(int) {} // Hides NamesHidden::hide_me(int).
};

También se puede utilizar una declaración de uso para cambiar los modificadores de acceso, siempre que la entidad importada fuera public o esté protected en la clase base.

struct ProMem {
  protected:
    void func() {}
};

struct BecomesPub : ProMem {
    using ProMem::func;
};

// ...

ProMem pm;
BecomesPub bp;

pm.func(); // Error: protected.
bp.func(); // Good.

De manera similar, si queremos llamar explícitamente a una función miembro de una clase específica en la jerarquía de herencia, podemos calificar el nombre de la función al llamar a la función, especificando esa clase por nombre.

struct One {
    virtual void f() { std::cout << "One." << std::endl; }
};

struct Two : One {
    void f() override {
        One::f(); // this->One::f();
        std::cout << "Two." << std::endl;
    }
};

struct Three : Two {
    void f() override {
        Two::f(); // this->Two::f();
        std::cout << "Three." << std::endl;
    }
};

// ...

Three t;

t.f();      // Normal syntax.
t.Two::f(); // Calls version of f() defined in Two.
t.One::f(); // Calls version of f() defined in One.

Funciones de miembro virtual

Las funciones miembro también pueden ser declaradas virtual . En este caso, si se llama a un puntero o referencia a una instancia, no se accederá directamente a ellos; más bien, buscarán la función en la tabla de funciones virtuales (una lista de funciones de punteros a miembros para funciones virtuales, más comúnmente conocida como vtable o vftable ), y la usarán para llamar a la versión apropiada para la dinámica de la instancia Tipo (real). Si la función se llama directamente, desde una variable de una clase, no se realiza ninguna búsqueda.

struct Base {
    virtual void func() { std::cout << "In Base." << std::endl; }
};

struct Derived : Base {
    void func() override { std::cout << "In Derived." << std::endl; }
};

void slicer(Base x) { x.func(); }

// ...

Base b;
Derived d;

Base *pb = &b, *pd = &d; // Pointers.
Base &rb = b, &rd = d;   // References.

b.func();   // Output:  In Base.
d.func();   // Output:  In Derived.

pb->func(); // Output:  In Base.
pd->func(); // Output:  In Derived.

rb.func();  // Output:  In Base.
rd.func();  // Output:  In Derived.

slicer(b);  // Output:  In Base.
slicer(d);  // Output:  In Base.

Tenga en cuenta que si bien pd es Base* , y rd es una Base& , llama func() en cualquiera de las dos llamadas Derived::func() lugar de Base::func() ; esto se debe a que vtable for Derived actualiza la entrada Base::func() para que, en cambio, apunte a Derived::func() . A la inversa, observe cómo pasar una instancia a slicer() siempre da como resultado que se llame a Base::func() , incluso cuando la instancia pasada es un Derived ; esto se debe a algo conocido como división de datos , donde pasar una instancia Derived a un parámetro Base por valor hace que la parte de la instancia Derived que no es inaccesible a una instancia Base .

Cuando una función de miembro se define como virtual, todas las funciones de miembro de clase derivadas con la misma firma lo anulan, independientemente de si la función de anulación se especifica como virtual o no. Sin embargo, esto puede hacer que las clases derivadas sean más difíciles de analizar para los programadores, ya que no hay ninguna indicación de qué función (es) es / son virtual .

struct B {
    virtual void f() {}
};

struct D : B {
    void f() {} // Implicitly virtual, overrides B::f.
                //  You'd have to check B to know that, though.
};

Sin embargo, tenga en cuenta que una función derivada solo reemplaza una función base si sus firmas coinciden; incluso si una función derivada se declara explícitamente virtual , en su lugar creará una nueva función virtual si las firmas no coinciden.

struct BadB {
    virtual void f() {}
};

struct BadD : BadB {
    virtual void f(int i) {} // Does NOT override BadB::f.
};
C ++ 11

A partir de C ++ 11, la intención de anular se puede hacer explícita con la override palabra clave sensible al contexto. Esto le dice al compilador que el programador espera que anule una función de clase base, lo que hace que el compilador omita un error si no anula nada.

struct CPP11B {
    virtual void f() {}
};

struct CPP11D : CPP11B {
    void f() override {}
    void f(int i) override {} // Error: Doesn't actually override anything.
};

Esto también tiene la ventaja de decirle a los programadores que la función es virtual y también declarada en al menos una clase base, lo que puede hacer que las clases complejas sean más fáciles de analizar.

Cuando una función se declara virtual y se define fuera de la definición de la clase, el especificador virtual debe incluirse en la declaración de la función y no repetirse en la definición.

C ++ 11

Esto también es válido para override .

struct VB {
    virtual void f(); // "virtual" goes here.
    void g();
};
/* virtual */ void VB::f() {} // Not here.
virtual void VB::g() {} // Error.

Si una clase base sobrecarga una función virtual , solo las sobrecargas que se especifiquen explícitamente como virtual serán virtuales.

struct BOverload {
    virtual void func() {}
    void func(int) {}
};

struct DOverload : BOverload {
    void func() override {}
    void func(int) {}
};

// ...

BOverload* bo = new DOverload;
bo->func(); // Calls DOverload::func().
bo->func(1); // Calls BOverload::func(int).

Para más información, vea el tema relevante .

Const Correccion

Uno de los usos principales de this calificador de const es la corrección const . Esta es la práctica de garantizar que solo los accesos que necesitan modificar un objeto puedan modificar el objeto, y que cualquier función (miembro o no miembro) que no necesite modificar un objeto no tiene acceso de escritura para eso. objeto (ya sea directa o indirectamente). Esto evita modificaciones no intencionales, haciendo que el código sea menos propenso a errores. También permite que cualquier función que no necesite modificar el estado pueda tomar un objeto const o no const , sin necesidad de reescribir o sobrecargar la función.

const corrección de const , debido a su naturaleza, comienza de abajo hacia arriba: cualquier función miembro de la clase que no necesite cambiar de estado se declara como const , de modo que se puede llamar en instancias de const . Esto, a su vez, permite pasar por referencia parámetros para ser declarado const cuando no necesitan ser modificados, lo que permite que las funciones que llevan ya sea const o no const objetos sin quejarse, y const -ness puede propagarse hacia el exterior en este manera. Debido a esto, los captadores suelen estar const , al igual que cualquier otra función que no necesite modificar el estado lógico.

class ConstIncorrect {
    Field fld;

  public:
    ConstIncorrect(const Field& f) : fld(f) {}     // Modifies.

    const Field& get_field()       { return fld; } // Doesn't modify; should be const.
    void set_field(const Field& f) { fld = f; }    // Modifies.

    void do_something(int i) {                     // Modifies.
        fld.insert_value(i);
    }
    void do_nothing()        { }                   // Doesn't modify; should be const.
};

class ConstCorrect {
    Field fld;

  public:
    ConstCorrect(const Field& f) : fld(f) {}       // Not const: Modifies.

    const Field& get_field() const { return fld; } // const: Doesn't modify.
    void set_field(const Field& f) { fld = f; }    // Not const: Modifies.

    void do_something(int i) {                     // Not const: Modifies.
        fld.insert_value(i);
    }
    void do_nothing() const  { }                   // const: Doesn't modify.
};

// ...

const ConstIncorrect i_cant_do_anything(make_me_a_field());
// Now, let's read it...
Field f = i_cant_do_anything.get_field();
  // Error: Loses cv-qualifiers, get_field() isn't const.
i_cant_do_anything.do_nothing();
  // Error: Same as above.
// Oops.

const ConstCorrect but_i_can(make_me_a_field());
// Now, let's read it...
Field f = but_i_can.get_field(); // Good.
but_i_can.do_nothing();          // Good.

Como lo ilustran los comentarios en ConstIncorrect y ConstCorrect , las funciones que califican para cv también sirven como documentación. Si una clase es const correcta, cualquier función que no está const puede suponerse con seguridad para cambiar de estado, y cualquier función que es const puede suponer con seguridad que no cambie de estado.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow