サーチ…
構文
//呼び出し中:
- variable.member_function();
- variable_pointer-> member_function();
//定義:
- ret_type class_name :: member_function()cv-qualifiers {
- 体;
- }
- ret_type class_name :: member_function()cv-qualifiers {
//プロトタイプ:
- class class_name {
- virt-specifier ret_typeメンバー関数()cv-qualifiers virt-specifier-seq;
- // virt-specifier:該当する場合、「仮想」。
- // cv-qualifiers: "const"および/または "volatile"(該当する場合)
- // virt-specifier-seq:該当する場合、「上書き」および/または「最終」。
- }
- class class_name {
備考
非static
メンバ関数は、 class
/ struct
/ union
体メンバ関数であり、特定のインスタンスに対して呼び出され、前記インスタンスに対して動作する。 static
メンバー関数とは異なり、インスタンスを指定しなければ呼び出すことはできません。
非静的メンバー関数
class
またはstruct
は、メンバ関数とメンバ変数を持つことができます。これらの関数は、スタンドアロン関数とほとんど同じ構文を持ち、クラス定義の内側または外側で定義できます。クラス定義の外で定義されている場合は、関数の名前の先頭にクラスの名前とスコープ( ::
演算子が付きます。
class CL {
public:
void definedInside() {}
void definedOutside();
};
void CL::definedOutside() {}
これらの関数は、ドット( .
)演算子を持つクラスのインスタンス(またはインスタンスへの参照)、または矢印( ->
)演算子を持つインスタンスへのポインタで呼び出され、各呼び出しは関数のインスタンスに結び付けられます呼び出されました。メンバ関数がインスタンス上で呼び出されると、( this
ポインタを介して)そのインスタンスのすべてのフィールドにアクセスできますが、それらのインスタンスがパラメータとして提供されている場合にのみ他のインスタンスのフィールドにアクセスできます。
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.
これらの関数は、変数や関数のアクセス修飾子に関係なく、メンバ変数やその他のメンバ関数にアクセスすることができます。コンパイラがクラスのコンパイルを開始する前に、クラス定義全体を解析する必要があるため、メンバー変数やその前に宣言されたメンバー関数にアクセスすることで、順不同で記述することもできます。
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)); }
};
カプセル化
メンバ関数の一般的な使用方法は、フィールドに直接アクセスするのではなく、 アクセッサ (一般にゲッタと呼ばれる)とミューテータ (一般にセッタと呼ばれる)を使用してカプセル化することです。
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);
}
};
クラス内では、非静的メンバー関数によってencapsulated
されたものに自由にアクセスできます。クラス外で、それへのアクセスを使用して、メンバ関数によって調節されるget_encapsulated()
それを読むし、 set_encapsulated()
それを修正します。これにより、変数の意図しない変更が防止されます。これは、独立した関数が読み書きに使用されるためです。 [ゲッターとセッターがカプセル化を提供したり断ち切っているかどうかについて多くの議論があり、どちらの主張にも良い議論がある。このような熱い議論はこの例の範囲外です。]
名前の非表示とインポート
基本クラスがオーバーロードされた関数のセットを提供し、派生クラスがそのセットに別のオーバーロードを追加すると、基本クラスによって提供されるすべてのオーバーロードが非表示になります。
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.
これは名前解決のルールによるものです:名前の検索中に正しい名前が見つかると、その名前のエンティティの正しいバージョンが見つからない場合でも( hd.f(s)
);このため、派生クラスの関数をオーバーロードすると、名前参照によって基本クラスのオーバーロードが検出されなくなります。これを避けるために、using宣言を使用して、基本クラスから派生クラスに名前を「インポート」することができます。これにより、名前の参照時に使用できるようになります。
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
派生クラスが、using宣言で名前をインポートするだけでなく、基本クラスの関数と同じシグネチャで関数を宣言すると、基本クラス関数は自動的にオーバーライドまたは非表示になります。
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).
};
インポートされたエンティティが基本クラスでpublic
またはprotected
れていれば、using宣言を使用してアクセス修飾子を変更することもできます。
struct ProMem {
protected:
void func() {}
};
struct BecomesPub : ProMem {
using ProMem::func;
};
// ...
ProMem pm;
BecomesPub bp;
pm.func(); // Error: protected.
bp.func(); // Good.
同様に、継承階層の特定のクラスからメンバー関数を明示的に呼び出したい場合、関数を呼び出すときに関数名を修飾し、そのクラスを名前で指定することができます。
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.
仮想メンバー関数
メンバー関数をvirtual
として宣言することもできvirtual
。この場合、ポインタまたはインスタンスへの参照で呼び出された場合、それらは直接アクセスされません。むしろ、仮想関数テーブル( vtable
またはvftable
としてよく知られている仮想関数のためのポインタ関数のメンバ関数のリスト)で関数をvftable
、それを使用してインスタンスの動的に適したバージョンを呼び出す(実際の)タイプです。関数がクラスの変数から直接呼び出された場合、ルックアップは実行されません。
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.
なおながらpd
あるBase*
、及びrd
あるBase&
呼び出すfunc()
の二つの呼び出しのいずれかにDerived::func()
の代わりに、 Base::func()
これは、 Derived
のvtable
がBase::func()
エントリをDerived::func()
指すように更新するためです。逆に、インスタンスをslicer()
渡すと、渡されたインスタンスがDerived
であっても、 Base::func()
が呼び出されることになります。これは、通過するデータスライスとして知られているものであるDerived
にインスタンスをBase
値によりパラメータが部分描画Derived
ないインスタンスBase
アクセスできないインスタンス。
メンバ関数が仮想として定義されている場合、オーバーライド関数がvirtual
として指定されているかどうかにかかわらず、同じシグネチャを持つすべての派生クラスメンバ関数が優先します。これは、プログラマが解析するために派生したクラスをより難しくすることができます。ただし、どの関数が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.
};
ただし、派生関数は、その署名が一致する場合にのみ基本関数をオーバーライドすることに注意してください。導出関数が明示的にvirtual
宣言されたとしても、署名が不一致であれば、代わりに新しい仮想関数を作成します。
struct BadB {
virtual void f() {}
};
struct BadD : BadB {
virtual void f(int i) {} // Does NOT override BadB::f.
};
C ++ 11以降、上書きする意向は、状況依存のキーワードのoverride
明示できoverride
。これは、プログラマが基本クラス関数をオーバーライドすることを期待していることをコンパイラに伝えます。コンパイラは何もオーバーライドしないとエラーを省略します。
struct CPP11B {
virtual void f() {}
};
struct CPP11D : CPP11B {
void f() override {}
void f(int i) override {} // Error: Doesn't actually override anything.
};
これはまた、関数が仮想であり、少なくとも1つの基本クラスでも宣言されていることをプログラマーに伝えるメリットがあります。複雑なクラスを解析しやすくすることができます。
関数がvirtual
宣言され、クラス定義の外で定義されるとき、 virtual
指定子は関数宣言に含まれなければならず、定義内で繰り返されません。
これはoverride
も当てはまりoverride
。
struct VB {
virtual void f(); // "virtual" goes here.
void g();
};
/* virtual */ void VB::f() {} // Not here.
virtual void VB::g() {} // Error.
基本クラスがvirtual
関数をオーバーロードする場合、 virtual
として明示的に指定されたオーバーロードのみがvirtual
になります。
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).
詳細については、該当するトピックを参照してください。
Const Correctness
this
cv修飾子の主な用途の1つは、 const
正しさです。これは、オブジェクトを変更する必要のあるアクセスのみがオブジェクトを変更できること 、およびオブジェクトを変更する必要のない(メンバまたは非メンバ)関数には、そのオブジェクトへの書き込みアクセス権がないことを保証する方法ですオブジェクト(直接的にも間接的にも)。これにより意図しない変更が防止され、コードのエラーが少なくなります。また、状態を変更する必要のない関数は、関数を書き換えたり、オーバーロードすることなく、 const
または非const
オブジェクトのいずれかを取ることができます。
const
正しさは、性質上、ボトムアップから始まります。状態を変更する必要のないクラスメンバ関数は、 const
インスタンスとしてconst
ようにconst
として宣言されています。これは順番に、渡された参照渡しパラメータを変更する必要がないときにconst
宣言することを可能にします。これにより、関数はconst
やnon- const
オブジェクトのいずれかを不平を言わずに取ることができ、 const
-nessはこの方法。このため、ゲッターは、論理状態を変更する必要のない他の関数と同様に、頻繁にconst
です。
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.
ConstIncorrect
とConstCorrect
コメントで示されているように、適切なcv修飾関数もドキュメントとして機能します。クラスがある場合はconst
正しい、ではありません任意の関数const
安全状態を変更すると仮定することができ、ある任意の関数const
安全な状態を変更しないと仮定することができます。