Szukaj…


Składnia

  • // Wywołanie:

    • zmienna.member_function ();
    • zmienna_pointer-> member_function ();
  • // Definicja:

    • ret_type nazwa_klasy :: member_function () cv-qualifiers {
      • ciało;
    • }
  • // Prototyp:

    • klasa nazwa_klasy {
      • virt-specifier ret_type member_function () cv-kwalifikatory virt-specifier-seq;
      • // virt-specifier: „virtual”, jeśli dotyczy.
      • // kwalifikatory cv: „const” i / lub „volatile”, jeśli dotyczy.
      • // virt-specifier-seq: „override” i / lub „final”, jeśli dotyczy.
    • }

Uwagi

Nie- static funkcji element jest class / struct / union funkcja człon, który nazywany jest w szczególnym przypadku, pracuje na wymienionym przypadku. W przeciwieństwie do static funkcji składowych, nie można go wywołać bez określenia instancji.

Informacje na temat klas, struktur i związków znajdują się w temacie nadrzędnym .

Niestatyczne funkcje składowe

class lub struct może mieć funkcje składowe, a także zmienne składowe. Funkcje te mają składnię w większości podobną do funkcji autonomicznych i można je zdefiniować w ramach definicji klasy lub poza nią; jeśli zdefiniowano poza definicją klasy, nazwa funkcji jest poprzedzona nazwą klasy i operatorem scope ( :: :).

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

Funkcje te są wywoływane w instancji (lub odwołaniu do instancji) klasy za pomocą operatora kropki ( . ) Lub wskaźnika do instancji za pomocą operatora strzałki ( -> ), a każde wywołanie jest powiązane z instancją funkcją został wezwany; wywołanie funkcji elementu w instancji ma dostęp do wszystkich pól tej instancji (za pomocą this wskaźnika ), ale może uzyskać dostęp do pól innych instancji tylko wtedy, gdy te instancje są dostarczone jako parametry.

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.

Funkcje te mają dostęp do zmiennych składowych i / lub innych funkcji składowych, niezależnie od modyfikatora dostępu do zmiennej lub funkcji. Można je również zapisywać poza kolejnością, uzyskując dostęp do zmiennych składowych i / lub wywołując funkcje składowe zadeklarowane przed nimi, ponieważ cała definicja klasy musi zostać przeanalizowana, zanim kompilator będzie mógł zacząć kompilować klasę.

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

Kapsułkowanie

Powszechnym zastosowaniem funkcji składowych jest enkapsulacja przy użyciu akcesorium (powszechnie nazywanego getter) i mutatora (powszechnie znanego jako setter) zamiast bezpośredniego dostępu do pól.

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

Wewnątrz klasy dostęp do encapsulated można uzyskać za pomocą dowolnej niestatycznej funkcji składowej; poza klasą dostęp do niego jest regulowany przez funkcje get_encapsulated() , za pomocą get_encapsulated() do odczytu i set_encapsulated() do modyfikacji. Zapobiega to niezamierzonym modyfikacjom zmiennej, ponieważ do jej odczytu i zapisu używane są osobne funkcje. [Istnieje wiele dyskusji na temat tego, czy osoby pobierające i ustawiające zapewniają enkapsulację, czy też ją przerywają, z dobrymi argumentami dla obu twierdzeń; taka gorąca debata jest poza zakresem tego przykładu.]

Ukrywanie nazw i importowanie

Gdy klasa podstawowa udostępnia zestaw przeciążonych funkcji, a klasa pochodna dodaje kolejne przeciążenie do zestawu, ukrywa to wszystkie przeciążenia zapewniane przez klasę podstawową.

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.

Wynika to z reguł rozpoznawania nazw: podczas wyszukiwania nazwy, gdy zostanie znaleziona poprawna nazwa, przestajemy szukać, nawet jeśli wyraźnie nie znaleźliśmy poprawnej wersji encji o tej nazwie (na przykład z hd.f(s) ); z tego powodu przeciążenie funkcji w klasie pochodnej zapobiega wykrywaniu przeciążeń w klasie podstawowej przez wyszukiwanie nazw. Aby tego uniknąć, można użyć deklaracji użycia do „importowania” nazw z klasy podstawowej do klasy pochodnej, aby były one dostępne podczas wyszukiwania nazw.

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

Jeśli klasa pochodna importuje nazwy z deklaracją użycia, ale także deklaruje funkcje z taką samą sygnaturą jak funkcje w klasie podstawowej, funkcje klasy podstawowej zostaną po cichu zastąpione lub ukryte.

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

Deklaracja użycia może być również wykorzystana do zmiany modyfikatorów dostępu, pod warunkiem, że importowana jednostka była public lub protected w klasie podstawowej.

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

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

// ...

ProMem pm;
BecomesPub bp;

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

Podobnie, jeśli jawnie chcemy wywołać funkcję członka z określonej klasy w hierarchii dziedziczenia, możemy zakwalifikować nazwę funkcji podczas wywoływania funkcji, określając tę klasę według nazwy.

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.

Funkcje wirtualnego członka

Funkcje składowe można również zadeklarować jako virtual . W takim przypadku wywołanie wskaźnika lub odwołania do instancji nie spowoduje bezpośredniego dostępu do nich; raczej vftable funkcję w wirtualnej tabeli funkcji (lista funkcji wskaźników do elementów członkowskich dla funkcji wirtualnych, bardziej znana jako vtable lub vftable ) i użyją tej funkcji do wywołania wersji odpowiedniej dla dynamiki instancji (rzeczywisty) typ. Jeśli funkcja jest wywoływana bezpośrednio ze zmiennej klasy, wyszukiwanie nie jest wykonywane.

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.

Zauważ, że podczas gdy pd to Base* , a rd to Base& , wywoływanie func() w jednym z dwóch wywołań Derived::func() zamiast Base::func() ; vtable tak, ponieważ vtable dla Derived aktualizuje wpis Base::func() aby zamiast tego wskazywał na Derived::func() . I odwrotnie, zwróć uwagę, że przekazanie instancji do slicer() zawsze powoduje wywołanie Base::func() , nawet jeśli przekazana instancja jest Derived ; dzieje się tak z powodu czegoś zwanego dzieleniem danych , gdzie przekazanie instancji Derived do parametru Base przez wartość powoduje, że część instancji Derived która nie jest instancją Base niedostępna.

Gdy funkcja członka jest zdefiniowana jako wirtualna, wszystkie pochodne funkcje klasy z tą samą sygnaturą zastępują ją, niezależnie od tego, czy funkcja zastępująca jest określona jako virtual czy nie. Może to jednak utrudniać analizowanie klas pochodnych, ponieważ nie ma wskazań, które funkcje są 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.
};

Należy jednak pamiętać, że funkcja pochodna zastępuje funkcję podstawową tylko wtedy, gdy ich podpisy są zgodne; nawet jeśli funkcja pochodna zostanie jawnie zadeklarowana jako virtual , zamiast tego utworzy nową funkcję wirtualną, jeśli podpisy będą niezgodne.

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

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

Począwszy od C ++ 11, zamiar zastąpienia może być jawny za pomocą kontekstowego override słowa kluczowego. Mówi to kompilatorowi, że programista oczekuje, że zastąpi funkcję klasy bazowej, co powoduje, że kompilator pomija błąd, jeśli niczego nie przesłania.

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

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

Ma to również tę zaletę, że mówi programistom, że funkcja jest zarówno wirtualna, jak i zadeklarowana w co najmniej jednej klasie bazowej, co może ułatwić analizę złożonych klas.

Gdy funkcja zostanie zadeklarowana jako virtual i zdefiniowana poza definicją klasy, specyfikator virtual musi zostać uwzględniony w deklaracji funkcji, a nie powtarzany w definicji.

C ++ 11

Dotyczy to również override .

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

Jeśli klasa podstawowa przeciąża funkcję virtual , tylko przeciążenia, które są jawnie określone jako virtual będą wirtualne.

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

Aby uzyskać więcej informacji, zobacz odpowiedni temat .

Const Poprawność

Jednym z podstawowych zastosowań this kwalifikatorów cv jest const poprawność . Jest to praktyka polegająca na zagwarantowaniu, że tylko dostęp, który musi zmodyfikować obiekt, jest w stanie zmodyfikować obiekt oraz że żadna funkcja (element członkowski lub nie będący członkiem), która nie musi modyfikować obiektu, nie ma dostępu do zapisu obiekt (bezpośrednio lub pośrednio). Zapobiega to niezamierzonym modyfikacjom, dzięki czemu kod jest mniej podatny na błędy. Pozwala także dowolnej funkcji, która nie musi modyfikować stanu, być w stanie przyjąć obiekt const lub non- const , bez potrzeby przepisywania lub przeciążania funkcji.

const poprawność ze względu na swój charakter zaczyna się od dołu do góry: Każda funkcja członka klasy, która nie musi zmieniać stanu, jest zadeklarowana jako const , aby można ją było wywoływać w instancjach const . To z kolei pozwala zadeklarować parametry przekazane przez odniesienie jako const gdy nie trzeba ich modyfikować, co pozwala funkcjom przyjmować obiekty const lub non- const bez narzekania, a const -ness może się w tym propagować na zewnątrz sposób. Z tego powodu gettery są często const , podobnie jak wszelkie inne funkcje, które nie muszą modyfikować stanu logicznego.

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.

Jak zilustrowano w komentarzach do ConstIncorrect i ConstCorrect , odpowiednie funkcje kwalifikujące cv służą również jako dokumentacja. Jeśli klasa jest const poprawna, można założyć, że każda funkcja, która nie jest const zmienia stan, a każdą funkcję, która jest const można bezpiecznie założyć, że nie zmienia stanu.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow