C++
Niet-statische ledenfuncties
Zoeken…
Syntaxis
// Bellen:
- variable.member_function ();
- variable_pointer-> member_function ();
// Definitie:
- ret_type class_name :: member_function () cv-qualifiers {
- lichaam;
- }
- ret_type class_name :: member_function () cv-qualifiers {
// Prototype:
- class class_name {
- virt-specifier ret_type member_function () cv-qualifiers virt-specifier-seq;
- // virt-specifier: "virtual", indien van toepassing.
- // cv-kwalificaties: "const" en / of "vluchtig", indien van toepassing.
- // virt-specifier-seq: "override" en / of "final", indien van toepassing.
- }
- class class_name {
Opmerkingen
Een niet- static
lidfunctie is een class
/ struct
/ union
struct
die op een bepaalde instantie wordt aangeroepen en op die instantie werkt. In tegenstelling tot static
lidfuncties, kan deze niet worden aangeroepen zonder een instantie op te geven.
Zie het hoofdonderwerp voor informatie over klassen, structuren en vakbonden.
Niet-statische ledenfuncties
Een class
of struct
kan zowel struct
als lidvariabelen hebben. Deze functies hebben een syntaxis die meestal vergelijkbaar is met zelfstandige functies en die binnen of buiten de klassedefinitie kunnen worden gedefinieerd; indien gedefinieerd buiten de klassedefinitie, wordt de naam van de functie voorafgegaan door de naam van de klasse en de operator scope ( ::
:).
class CL {
public:
void definedInside() {}
void definedOutside();
};
void CL::definedOutside() {}
Deze functies worden op een instantie (of verwijzing naar een instantie) van de klasse met de punt ( .
) Operator genoemd, of een aanwijzer naar een instantie met de pijl ( ->
) operator, en elke oproep is gekoppeld aan de instantie de functie werd opgeroepen; wanneer een lidfunctie wordt aangeroepen voor een instantie, heeft deze toegang tot alle velden van die instantie (via this
aanwijzer ), maar heeft hij alleen toegang tot de velden van andere instanties als die instanties worden geleverd als parameters.
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.
Deze functies hebben toegang tot lidvariabelen en / of andere lidfuncties, ongeacht de toegangsmodificatoren van de variabele of de functie. Ze kunnen ook buiten de orde worden geschreven, toegang krijgen tot lidvariabelen en / of aanroepende lidfuncties die daarvoor zijn gedeclareerd, omdat de volledige klassedefinitie moet worden ontleed voordat de compiler kan beginnen met het compileren van een klasse.
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)); }
};
inkapseling
Een algemeen gebruik van lidfuncties is voor inkapseling, met behulp van een accessor (algemeen bekend als een getter) en een mutator (algemeen bekend als een setter) in plaats van rechtstreeks toegang te krijgen tot velden.
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);
}
};
Binnen de klasse is encapsulated
vrij toegankelijk voor elke niet-statische lidfunctie; buiten de klasse wordt de toegang get_encapsulated()
geregeld door get_encapsulated()
, met behulp van get_encapsulated()
om het te lezen en set_encapsulated()
om het te wijzigen. Dit voorkomt onbedoelde wijzigingen van de variabele, omdat afzonderlijke functies worden gebruikt om deze te lezen en te schrijven. [Er zijn veel discussies over of getters en setters encapsulation bieden of breken, met goede argumenten voor beide claims; een dergelijk verhit debat valt buiten het bestek van dit voorbeeld.]
Naam verbergen en importeren
Wanneer een basisklasse een set overbelaste functies biedt en een afgeleide klasse nog een overbelasting aan de set toevoegt, worden alle overbelastingen van de basisklasse verborgen.
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.
Dit is te wijten aan regels voor naamomzetting: tijdens het opzoeken van de naam stoppen we met zoeken, zelfs als we duidelijk niet de juiste versie van de entiteit met die naam hebben gevonden (zoals bij hd.f(s)
); hierdoor voorkomt het overbelasten van de functie in de afgeleide klasse dat het opzoeken van namen de overbelastingen in de basisklasse ontdekt. Om dit te voorkomen, kan een gebruikendeclaratie worden gebruikt om namen uit de basisklasse in de afgeleide klasse te "importeren", zodat ze beschikbaar zijn tijdens het opzoeken van de naam.
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
Als een afgeleide klasse namen met een gebruikendeclaratie importeert, maar ook functies declareert met dezelfde handtekening als functies in de basisklasse, worden de basisklassefuncties stilzwijgend genegeerd of verborgen.
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).
};
Een gebruikendeclaratie kan ook worden gebruikt om toegangsmodificaties te wijzigen, op voorwaarde dat de geïmporteerde entiteit public
of protected
in de basisklasse.
struct ProMem {
protected:
void func() {}
};
struct BecomesPub : ProMem {
using ProMem::func;
};
// ...
ProMem pm;
BecomesPub bp;
pm.func(); // Error: protected.
bp.func(); // Good.
Evenzo, als we expliciet een lidfunctie uit een specifieke klasse in de overervingshiërarchie willen aanroepen, kunnen we de functienaam kwalificeren bij het aanroepen van de functie, door die klasse op naam op te geven.
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.
Virtuele ledenfuncties
Ledenfuncties kunnen ook virtual
worden verklaard. In dit geval zullen deze niet direct toegankelijk zijn als ze op een aanwijzer of verwijzing naar een instantie worden aangeroepen; in plaats daarvan zullen ze de functie opzoeken in de virtuele functietabel (een lijst met verwijzingen naar lid-functies voor virtuele functies, beter bekend als de vtable
of vftable
), en die gebruiken om de versie aan te roepen die geschikt is voor de dynamische dynamiek van de instantie (feitelijk) type. Als de functie rechtstreeks wordt aangeroepen vanuit een variabele van een klasse, wordt er niet opgezocht.
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.
Merk op dat, terwijl pd
Base*
, en rd
Base&
, func()
aanroept op een van de twee aanroepen Derived::func()
plaats van Base::func()
; dit komt omdat de vtable
voor Derived
het vtable
Base::func()
bijwerkt om in plaats daarvan te verwijzen naar Derived::func()
. Omgekeerd, merk op hoe het doorgeven van een instantie aan slicer()
altijd toe leidt dat Base::func()
wordt aangeroepen, zelfs wanneer de doorgegeven instantie een Derived
; dit komt door iets dat bekend staat als gegevenssegmentering , waarbij het door waarde doorgeven van een Derived
instantie in een parameter Base
op waarde het deel van de Derived
instantie dat geen Base
instantie is, ontoegankelijk maakt.
Wanneer een lidfunctie is gedefinieerd als virtueel, hebben alle afgeleide klassenlidfuncties met dezelfde handtekening voorrang, ongeacht of de vervangende functie is opgegeven als virtual
of niet. Dit kan afgeleide klassen echter moeilijker maken voor programmeurs om te parseren, omdat er geen indicatie is welke functie (s) virtual
is / zijn.
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.
};
Merk echter op dat een afgeleide functie alleen een basisfunctie overschrijft als hun handtekeningen overeenkomen; zelfs als een afgeleide functie expliciet virtual
wordt verklaard, zal deze in plaats daarvan een nieuwe virtuele functie maken als de handtekeningen niet overeenkomen.
struct BadB {
virtual void f() {}
};
struct BadD : BadB {
virtual void f(int i) {} // Does NOT override BadB::f.
};
Vanaf C ++ 11 kan de intentie om te negeren expliciet worden gemaakt met het contextgevoelige sleutelwoord override
. Dit vertelt de compiler dat het programmeerapparaat verwacht dat het een base class-functie overschrijft, waardoor de compiler een fout overslaat als deze niets overschrijft.
struct CPP11B {
virtual void f() {}
};
struct CPP11D : CPP11B {
void f() override {}
void f(int i) override {} // Error: Doesn't actually override anything.
};
Dit heeft ook het voordeel dat programmeurs worden verteld dat de functie zowel virtueel is als ook in ten minste één basisklasse wordt gedeclareerd, waardoor complexe klassen gemakkelijker kunnen worden ontleed.
Wanneer een functie virtual
wordt verklaard en buiten de klassedefinitie wordt gedefinieerd, moet de virtual
specificatie worden opgenomen in de functieverklaring en niet worden herhaald in de definitie.
Dit geldt ook voor override
.
struct VB {
virtual void f(); // "virtual" goes here.
void g();
};
/* virtual */ void VB::f() {} // Not here.
virtual void VB::g() {} // Error.
Als een basisklasse een virtual
functie overbelast, zijn alleen overbelastingen die expliciet als virtual
zijn gespecificeerd, virtueel.
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).
Zie het relevante onderwerp voor meer informatie.
Const Correctheid
Een van de belangrijkste toepassingen voor this
cv-kwalificaties is const
correctheid . Dit is de praktijk te garanderen dat alleen toegangen noodzaak om een object aan te passen zijn in staat om het object te wijzigen, en dat eventuele (lid of niet-lid) functie die niet hoeft om een object te wijzigen heeft geen schrijftoegang tot die object (direct of indirect). Dit voorkomt onbedoelde wijzigingen, waardoor code minder foutgevoelig wordt. Het staat ook elke functie toe die de status niet hoeft te wijzigen om een const
of niet- const
object te kunnen nemen, zonder de functie te moeten herschrijven of overbelasten.
const
correctheid, const
vanwege zijn aard, begint van onderaf: elke functie van een lid van een klasse die de status niet hoeft te veranderen, wordt als const
verklaard , zodat deze op instanties van const
kan worden opgeroepen. Dit op zijn beurt maakt doorgegeven door verwijzing parameters worden verklaard const
als ze niet te worden gewijzigd, waardoor functies ofwel const
of niet- const
objecten zonder klagen en const
ness naar buiten kan voortplanten in deze manier. Hierdoor zijn getters vaak const
, net als andere functies die de logische status niet hoeven te wijzigen.
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.
Zoals geïllustreerd door de opmerkingen over ConstIncorrect
en ConstCorrect
, dienen functies voor cv-kwalificatie ook als documentatie. Als een klasse const
correct is, kan van elke functie die niet const
is veilig worden aangenomen dat deze van staat verandert, en van elke functie die const
kan veilig worden aangenomen dat deze de status niet verandert.