Ricerca…


Sintassi

  • // Chiamando:

    • variable.member_function ();
    • variable_pointer-> member_function ();
  • // Definizione:

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

    • classe class_name {
      • virt-specificatore ret_type member_function () cv-qualifiers virt-specificatore-seq;
      • // virt-specificatore: "virtuale", se applicabile.
      • // cv-qualifiers: "const" e / o "volatile", se applicabile.
      • // virt-specifier-seq: "override" e / o "final", se applicabile.
    • }

Osservazioni

Un non static funzione membro è una class / struct / union funzione membro, che è chiamato un caso particolare, e opera su detta istanza. A differenza delle funzioni membro static , non può essere chiamato senza specificare un'istanza.

Per informazioni su classi, strutture e sindacati, consultare l'argomento principale .

Funzioni membro non statiche

Una class o una struct possono avere funzioni membro e variabili membro. Queste funzioni hanno una sintassi per lo più simile alle funzioni standalone e possono essere definite all'interno o all'esterno della definizione della classe; se definito al di fuori della definizione della classe, il nome della funzione è preceduto dal nome della classe e dall'operatore scope ( :: :).

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

Queste funzioni sono richiamate su un'istanza (o un riferimento a un'istanza) della classe con l'operatore punto ( . ) O un puntatore a un'istanza con l'operatore freccia ( -> ) e ogni chiamata è legata all'istanza la funzione è stato chiamato; quando una funzione membro viene chiamata su un'istanza, ha accesso a tutti i campi dell'istanza (tramite il puntatore this ), ma può accedere solo ai campi di altre istanze se tali istanze vengono fornite come parametri.

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.

Queste funzioni possono accedere alle variabili membro e / o alle altre funzioni membro, indipendentemente dalla variabile o dai modificatori di accesso della funzione. Possono anche essere scritti fuori ordine, accedere alle variabili membro e / o chiamare le funzioni membro dichiarate prima di loro, poiché l'intera definizione di classe deve essere analizzata prima che il compilatore possa iniziare a compilare una classe.

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

incapsulamento

Un uso comune delle funzioni membro è per l'incapsulamento, utilizzando un accessore (comunemente noto come getter) e un mutatore (comunemente noto come setter) invece di accedere direttamente ai campi.

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

All'interno della classe, encapsulated può essere liberamente accessibile da qualsiasi funzione membro non statico; al di fuori della classe, l'accesso ad esso è regolato dalle funzioni membro, usando get_encapsulated() per leggerlo e set_encapsulated() per modificarlo. Ciò impedisce modifiche non intenzionali alla variabile, poiché vengono utilizzate funzioni separate per leggerlo e scriverlo. [Ci sono molte discussioni sul fatto che getter e setter forniscano o interrompano l'incapsulamento, con buoni argomenti per entrambe le affermazioni; un dibattito così acceso non rientra negli scopi di questo esempio.]

Nascondere e importare il nome

Quando una classe base fornisce un insieme di funzioni sovraccariche e una classe derivata aggiunge un altro sovraccarico all'insieme, questo nasconde tutti i sovraccarichi forniti dalla classe 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.

Ciò è dovuto al nome delle regole di risoluzione: durante la ricerca del nome, una volta trovato il nome corretto, smettiamo di cercare, anche se chiaramente non abbiamo trovato la versione corretta dell'entità con quel nome (come con hd.f(s) ); a causa di ciò, sovraccaricare la funzione nella classe derivata impedisce la ricerca del nome dalla scoperta degli overload nella classe base. Per evitare ciò, è possibile utilizzare una dichiarazione di utilizzo per "importare" i nomi dalla classe base nella classe derivata, in modo che siano disponibili durante la ricerca del nome.

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

Se una classe derivata importa nomi con una use-declaration, ma dichiara anche funzioni con la stessa firma delle funzioni nella classe base, le funzioni della classe base saranno silenziate o nascoste in modo silenzioso.

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

È inoltre possibile utilizzare una dichiarazione di utilizzo per modificare i modificatori di accesso, a condizione che l'entità importata fosse public o protected nella classe base.

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

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

// ...

ProMem pm;
BecomesPub bp;

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

Allo stesso modo, se vogliamo chiamare esplicitamente una funzione membro da una classe specifica nella gerarchia dell'ereditarietà, possiamo qualificare il nome della funzione quando chiamiamo la funzione, specificando quella classe per nome.

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.

Funzioni dei membri virtuali

Le funzioni membro possono anche essere dichiarate virtual . In questo caso, se richiamati su un puntatore o su un riferimento a un'istanza, non saranno accessibili direttamente; piuttosto, cercheranno la funzione nella tabella delle funzioni virtuali (un elenco di funzioni da puntatore a membro per le funzioni virtuali, più comunemente noto come vtable o vftable ) e la useranno per chiamare la versione appropriata per la dinamica dell'istanza (effettivo) tipo. Se la funzione viene chiamata direttamente, da una variabile di una classe, non viene eseguita alcuna ricerca.

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.

Si noti che mentre pd è Base* , e rd è una Base& , chiamando func() su una delle due chiamate Derived::func() invece di Base::func() ; questo perché il vtable per Derived aggiorna la voce Base::func() per puntare invece a Derived::func() . Al contrario, si noti come passare un'istanza a slicer() comporta sempre il richiamo di Base::func() , anche quando l'istanza passata è Derived ; ciò è dovuto a qualcosa noto come slicing dei dati , in cui il passaggio di un'istanza Derived in un parametro Base per valore rende la parte dell'istanza Derived che non è un'istanza di Base inaccessibile.

Quando una funzione membro è definita come virtuale, tutte le funzioni membro membro derivate con la stessa firma lo sovrascrivono, indipendentemente dal fatto che la funzione di override sia specificata come virtual o meno. Questo può rendere le classi derivate più difficili da analizzare per i programmatori, tuttavia, poiché non vi è alcuna indicazione su quale funzione sia / sono 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.
};

Si noti, tuttavia, che una funzione derivata sovrascrive una funzione di base solo se le loro firme corrispondono; anche se una funzione derivata è dichiarata esplicitamente virtual , creerà invece una nuova funzione virtuale se le firme non corrispondono.

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

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

A partire da C ++ 11, l'intento di sovrascrivere può essere reso esplicito con la override parola chiave sensibile al contesto. Questo dice al compilatore che il programmatore si aspetta che sovrascriva una funzione di classe base, che fa sì che il compilatore ometta un errore se non sovrascrive nulla.

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

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

Ciò ha anche il vantaggio di dire ai programmatori che la funzione è sia virtuale, sia anche dichiarata in almeno una classe base, che può rendere più semplice l'analisi di classi complesse.

Quando una funzione è dichiarata virtual e definita al di fuori della definizione della classe, lo specificatore virtual deve essere incluso nella dichiarazione della funzione e non ripetuto nella definizione.

C ++ 11

Questo vale anche per l' override .

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

Se una classe base sovraccarica una funzione virtual , solo gli overload specificati come virtual saranno virtuali.

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).

Per ulteriori informazioni, consultare l'argomento pertinente .

Const Correctness

Uno degli usi principali di this cv-qualificatore è la correttezza const . Questa è la pratica di garantire che solo gli accessi che devono modificare un oggetto siano in grado di modificare l'oggetto e che qualsiasi funzione (membro o non membro) che non ha bisogno di modificare un oggetto non abbia accesso in scrittura a tale oggetto (direttamente o indirettamente). Ciò impedisce modifiche involontarie, rendendo il codice meno errorprone. Inoltre, consente a qualsiasi funzione che non ha bisogno di modificare lo stato di essere in grado di prendere un oggetto const o non const , senza la necessità di riscrivere o sovraccaricare la funzione.

const correttezza const , per sua natura, inizia dal basso verso l'alto: qualsiasi funzione membro della classe che non ha bisogno di cambiare stato è dichiarata come const , in modo che possa essere richiamata su istanze const . Questo, a sua volta, consente ai parametri passati per riferimento di essere dichiarati const quando non hanno bisogno di essere modificati, il che consente alle funzioni di prendere gli oggetti const o non const senza lamentarsi, e la const -ness può propagarsi all'esterno in questo maniera. A causa di ciò, i getter sono frequentemente const , come lo sono tutte le altre funzioni che non hanno bisogno di modificare lo stato logico.

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.

Come illustrato dai commenti su ConstIncorrect e ConstCorrect , anche le funzioni di cv-qualifying servono da documentazione. Se una classe è const corretta, qualsiasi funzione che non è const può essere assunta in modo sicuro per cambiare stato, e qualsiasi funzione che è const può essere considerata come sicura per non cambiare stato.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow