サーチ…
構文
- variable.member_var =定数。
- variable.member_function();
- variable_pointer-> member_var =定数;
- variable_pointer-> member_function();
備考
唯一の違いがありますstruct
とclass
のキーワードがデフォルトで、のメンバ変数、メンバ関数、および基底クラスということであるstruct
ありpublic
にしながら、 class
彼らはprivate
。 C ++のプログラマは、コンストラクタとデストラクタを持っていればそれをクラスと呼ぶ傾向があり、それ自身のインバリアントを強制することができます。単純な値の集まりであれば構造体ですが、C ++言語自体は区別しません。
クラスの基礎
クラスはユーザー定義型です。クラスは、 class
、 struct
またはunion
キーワードで導入されています。口語使用では、「クラス」という用語は通常、非ユニオンクラスのみを指します。
クラスは、 クラスメンバーのコレクションであり、次のようになります。
- メンバー変数(「フィールド」とも呼ばれる)
- メンバー関数(「メソッド」とも呼ばれる)
- メンバ型または型定義(例えば、 "入れ子クラス")、
- メンバテンプレート(種類:変数、関数、クラス、エイリアステンプレート)
class
やstruct
メンバーおよび塩基のためのデフォルトのアクセス指定子はと宣言されたクラスのための「プライベート」であることを除いて、キーワード、 クラスキーと呼ばれ、主に互換性があり、 class
で宣言されたクラスのためのキーと「公共」 struct
またはunion
キー(参照: アクセス修飾子 )。
たとえば、次のコードスニペットは同じです。
struct Vector
{
int x;
int y;
int z;
};
// are equivalent to
class Vector
{
public:
int x;
int y;
int z;
};
クラス `を宣言することで、新しい型がプログラムに追加され、そのクラスのオブジェクトを次のようにインスタンス化することができます。
Vector my_vector;
クラスのメンバーは、ドット構文を使用してアクセスされます。
my_vector.x = 10;
my_vector.y = my_vector.x + 1; // my_vector.y = 11;
my_vector.z = my_vector.y - 4; // my:vector.z = 7;
アクセス指定子
アクセス指定子として機能する3つのキーワードがあります 。これらは、指定子に続くクラスメンバへのアクセスを、別の指定子がアクセスレベルを再び変更するまで制限します。
キーワード | 説明 |
---|---|
public | 誰もがアクセスできます |
protected | クラス自体、派生したクラスとフレンドだけがアクセスできます |
private | クラス自体と友人のみがアクセスできます |
型がclass
キーワードを使用して定義されている場合、デフォルトのアクセス指定子はprivate
ですが、型がstruct
キーワードを使用して定義されている場合、デフォルトのアクセス指定子はpublic
です。
struct MyStruct { int x; };
class MyClass { int x; };
MyStruct s;
s.x = 9; // well formed, because x is public
MyClass c;
c.x = 9; // ill-formed, because x is private
アクセス指定子は主に内部フィールドとメソッドへのアクセスを制限するために使用されます。たとえば、変数を直接参照するのではなく、ゲッターとセッターを強制的に使用するなど、
class MyClass {
public: /* Methods: */
int x() const noexcept { return m_x; }
void setX(int const x) noexcept { m_x = x; }
private: /* Fields: */
int m_x;
};
protected
を使用すると、型の特定の機能に派生クラスのみがアクセスできるようにするのに便利です。たとえば、次のコードでは、メソッドcalculateValue()
は、 FortyTwo
などの基本クラスPlus2Base
から派生したクラスにのみアクセスできます。
struct Plus2Base {
int value() noexcept { return calculateValue() + 2; }
protected: /* Methods: */
virtual int calculateValue() noexcept = 0;
};
struct FortyTwo: Plus2Base {
protected: /* Methods: */
int calculateValue() noexcept final override { return 40; }
};
friend
キーワードを使用すると、保護されたメンバーと非公開のメンバーにアクセスするための関数や型にアクセス例外を追加できます。
public
、 protected
、およびprivate
キーワードは、基本クラスのサブオブジェクトへのアクセスを許可または制限するためにも使用できます。 継承の例を参照してください。
継承
クラス/構造体は継承関係を持つことができます。
クラス/構造体B
がクラス/構造体A
から継承する場合、これはB
が親A
持つことを意味します。 B
はA
派生クラス/構造体、 A
は基本クラス/構造体です。
struct A
{
public:
int p1;
protected:
int p2;
private:
int p3;
};
//Make B inherit publicly (default) from A
struct B : A
{
};
クラス/構造体には3つの継承形式があります。
-
public
-
private
-
protected
デフォルトの継承はメンバーのデフォルトの可視性と同じであることに注意してくださいstruct
キーワードを使用する場合はpublic
を、 class
キーワードをprivate
にします。
struct
からclass
派生させることも可能です(またはその逆)。この場合、デフォルトの継承は子によって制御されているので、 struct
から派生するclass
、パブリック継承にデフォルト設定されます、そしてclass
から派生したstruct
、デフォルトでは、民間の継承を持つことになります。
public
継承:
struct B : public A // or just `struct B : A`
{
void foo()
{
p1 = 0; //well formed, p1 is public in B
p2 = 0; //well formed, p2 is protected in B
p3 = 0; //ill formed, p3 is private in A
}
};
B b;
b.p1 = 1; //well formed, p1 is public
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible
private
継承:
struct B : private A
{
void foo()
{
p1 = 0; //well formed, p1 is private in B
p2 = 0; //well formed, p2 is private in B
p3 = 0; //ill formed, p3 is private in A
}
};
B b;
b.p1 = 1; //ill formed, p1 is private
b.p2 = 1; //ill formed, p2 is private
b.p3 = 1; //ill formed, p3 is inaccessible
protected
継承:
struct B : protected A
{
void foo()
{
p1 = 0; //well formed, p1 is protected in B
p2 = 0; //well formed, p2 is protected in B
p3 = 0; //ill formed, p3 is private in A
}
};
B b;
b.p1 = 1; //ill formed, p1 is protected
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible
protected
継承は許可protected
ていますが、実際の使用はまれです。アプリケーションでprotected
継承がどのように使用されるかの1つの例は、部分的な基底クラスの特殊化(通常、「制御多型」と呼ばれます)です。
OOPが比較的新しい時、(公的)継承はしばしば「IS-A」関係をモデル化すると言われました。つまり、パブリック継承は、派生クラスのインスタンスが基本クラスのインスタンスである場合にのみ正しいものです。
これは後でLiskov Substitution Principleに洗練されました。パブリック継承は、派生クラスのインスタンスが可能な状況下で基本クラスのインスタンスに置き換えられる(そしてそれでも意味がある)場合にのみ使用してください。
私的継承は、典型的には完全に異なる関係(「HAS-A」関係とも呼ばれる)の観点から実装されていると言われています。たとえば、 Stack
クラスはVector
クラスからプライベートに継承できます。プライベート継承は、パブリック継承よりも集約に非常に似ています。
保護された継承はほとんど使用されておらず、どのような関係が具体化しているかについて一般的な合意はありません。
仮想継承
継承を使用する場合は、 virtual
キーワードを指定できます。
struct A{};
struct B: public virtual A{};
クラスB
が仮想基底A
持つとき、それはA
がほとんどの派生クラスの継承ツリーに常駐することを意味します。したがって、派生クラスのほとんどがその仮想基底の初期化を担当します。
struct A
{
int member;
A(int param)
{
member = param;
}
};
struct B: virtual A
{
B(): A(5){}
};
struct C: B
{
C(): /*A(88)*/ {}
};
void f()
{
C object; //error since C is not initializing it's indirect virtual base `A`
}
/*A(88)*/
をコメントアウトすると、 C
は現在間接仮想基底A
初期化しているのでエラーは発生しません。
また、変数object
作成するobject
、ほとんどの派生クラスはC
であるため、 C
はA
作成コンストラクタの作成を担当し、 A::member
値は5
ではなく88
であることに注意してください(これは、タイプB
オブジェクトを作成します)。
ダイヤモンドの問題を解決するときに便利です。
A A A
/ \ | |
B C B C
\ / \ /
D D
virtual inheritance normal inheritance
B
とC
の両方が継承A
、及びD
から継承B
及びC
ので、 の2つのインスタンスが存在するA
でD
!これは、コンパイラがどのクラスからそのメンバ( B
継承するものか、 C
継承されるもの)にアクセスするかを知る方法がないため、 A
からD
メンバにアクセスするときに曖昧になります。 。
仮想継承はこの問題を解決します。仮想基底はほとんどの派生オブジェクトにのみ存在するため、 D
はA
インスタンスが1つしかありません。
struct A
{
void foo() {}
};
struct B : public /*virtual*/ A {};
struct C : public /*virtual*/ A {};
struct D : public B, public C
{
void bar()
{
foo(); //Error, which foo? B::foo() or C::foo()? - Ambiguous
}
};
コメントを削除すると、あいまいさが解消されます。
多重継承
単一の継承を除いて:
class A {};
class B : public A {};
また、複数の継承を持つこともできます。
class A {};
class B {};
class C : public A, public B {};
C
はA
とB
から同時に継承します。
注:複数の継承class
またはstruct
で同じ名前が使用されている場合は、あいまいさにつながる可能性があります。注意してください!
多重継承におけるあいまい性
いくつかのケースでは多重継承が役立つかもしれませんが、多重継承を使用しているときに奇妙な問題が発生することがあります。
たとえば、2つの基本クラスには、派生クラスでオーバーライドされない同じ名前の関数があり、派生クラスのオブジェクトを使用してその関数にアクセスするコードを記述すると、コンパイラはエラーを表示します。多重継承におけるこのタイプのあいまいさのコードを以下に示します。
class base1
{
public:
void funtion( )
{ //code for base1 function }
};
class base2
{
void function( )
{ // code for base2 function }
};
class derived : public base1, public base2
{
};
int main()
{
derived obj;
// Error because compiler can't figure out which function to call
//either function( ) of base1 or base2 .
obj.function( )
}
しかし、この問題は、スコープ解決関数を使用して、base1またはbase2のいずれのクラスにどの関数を指定するかを解決することができます。
int main()
{
obj.base1::function( ); // Function of class base1 is called.
obj.base2::function( ); // Function of class base2 is called.
}
クラスメンバーへのアクセス
クラスのオブジェクトのメンバー変数とメンバ関数にアクセスするには、 .
演算子が使用されます。
struct SomeStruct {
int a;
int b;
void foo() {}
};
SomeStruct var;
// Accessing member variable a in var.
std::cout << var.a << std::endl;
// Assigning member variable b in var.
var.b = 1;
// Calling a member function.
var.foo();
ポインタを介してクラスのメンバにアクセスする場合、 ->
演算子が一般的に使用されます。別の方法として、インスタンスを逆参照することもできます.
これはあまり一般的ではありませんが、
struct SomeStruct {
int a;
int b;
void foo() {}
};
SomeStruct var;
SomeStruct *p = &var;
// Accessing member variable a in var via pointer.
std::cout << p->a << std::endl;
std::cout << (*p).a << std::endl;
// Assigning member variable b in var via pointer.
p->b = 1;
(*p).b = 1;
// Calling a member function via a pointer.
p->foo();
(*p).foo();
静的クラスメンバーにアクセスするときは、 ::
演算子が使用されますが、クラスのインスタンスではなくクラスの名前に使用されます。代わりに、スタティックメンバは、インスタンスまたはインスタンスへのポインタからを使用してアクセスすることができます.
または->
演算子であり、非静的メンバーにアクセスするのと同じ構文です。
struct SomeStruct {
int a;
int b;
void foo() {}
static int c;
static void bar() {}
};
int SomeStruct::c;
SomeStruct var;
SomeStruct* p = &var;
// Assigning static member variable c in struct SomeStruct.
SomeStruct::c = 5;
// Accessing static member variable c in struct SomeStruct, through var and p.
var.a = var.c;
var.b = p->c;
// Calling a static member function.
SomeStruct::bar();
var.bar();
p->bar();
バックグラウンド
メンバーアクセス演算子であるため、 ->
演算子が必要です.
逆参照演算子*
よりも優先されます。
一つは、期待*pa
間接参照であろうp
(オブジェクトへの参照を生じるp
とそのメンバーのアクセスに向いています) 。 a
しかし、実際にa
、 p
のメンバーa
にアクセスしてから逆参照しようとします。 Ie *pa
は*(pa)
と等価です。まず、2つの事実のため、上記の例では、これはコンパイラのエラーになりp
ポインタで、メンバーはありません。 a
第2に、 a
は整数であるため、逆参照することはできません。
この問題のめったに使われない解決方法は、優先順位を明示的に制御することです: (*p).a
代わりに、 ->
演算子はほとんど常に使用されます。ポインタを最初に参照解除してからアクセスするのは、短い手です。 Ie (*p).a
はp->a
とまったく同じです。
::
演算子はスコープ演算子で、名前空間のメンバにアクセスするのと同じ方法で使用されます。これは、静的クラスメンバーがそのクラスのスコープにあると見なされますが、そのクラスのインスタンスのメンバーとはみなされないためです。通常の使用.
歴史的な理由から、インスタンスメンバではないにもかかわらず、静的メンバにも->
が許可されています。これは、呼び出し側が特定のメンバ関数が静的であるか非静的であるかを考慮する必要がないため、テンプレートに汎用コードを記述するために使用されます。
プライベート継承:基本クラスインタフェースの制限
プライベート継承は、クラスのパブリックインターフェイスを制限する必要がある場合に便利です。
class A {
public:
int move();
int turn();
};
class B : private A {
public:
using A::turn;
};
B b;
b.move(); // compile error
b.turn(); // OK
このアプローチは、Aポインタまたは参照にキャストすることによって、Aパブリックメソッドへのアクセスを効率的に防止します。
B b;
A& a = static_cast<A&>(b); // compile error
公開継承の場合、そのようなキャスティングは、Bを誘導することを避けるための代替方法にもかかわらず、すべてのA公開メソッドへのアクセスを提供します。
class B : public A {
private:
int move();
};
またはプライベートを使用して:
class B : public A {
private:
using A::move;
};
どちらの場合も可能です:
B b;
A& a = static_cast<A&>(b); // OK for public inheritance
a.move(); // OK
最終クラスと構造体
クラスの派生は、 final
指定子で禁止することができます。最終的なクラスを宣言しましょう:
class A final {
};
これをサブクラス化しようとするとコンパイルエラーが発生します:
// Compilation error: cannot derive from final class:
class B : public A {
};
最終的なクラスは、クラス階層のどこにでも表示されます。
class A {
};
// OK.
class B final : public A {
};
// Compilation error: cannot derive from final class B.
class C : public B {
};
友情
friend
キーワードは、クラスのスコープ外で定義されていても、他のクラスや関数にそのクラスのプライベートメンバーや保護されたメンバーへのアクセスを与えるために使用されます。
class Animal{
private:
double weight;
double height;
public:
friend void printWeight(Animal animal);
friend class AnimalPrinter;
// A common use for a friend function is to overload the operator<< for streaming.
friend std::ostream& operator<<(std::ostream& os, Animal animal);
};
void printWeight(Animal animal)
{
std::cout << animal.weight << "\n";
}
class AnimalPrinter
{
public:
void print(const Animal& animal)
{
// Because of the `friend class AnimalPrinter;" declaration, we are
// allowed to access private members here.
std::cout << animal.weight << ", " << animal.height << std::endl;
}
}
std::ostream& operator<<(std::ostream& os, Animal animal)
{
os << "Animal height: " << animal.height << "\n";
return os;
}
int main() {
Animal animal = {10, 5};
printWeight(animal);
AnimalPrinter aPrinter;
aPrinter.print(animal);
std::cout << animal;
}
10
10, 5
Animal height: 5
ネストされたクラス/構造
class
またはstruct
は、自身の中に別のclass
/ struct
定義を含むこともできます。これは "入れ子になったクラス"と呼ばれます。この状況では、包含クラスは「包含クラス」と呼ばれます。ネストされたクラス定義は、囲むクラスのメンバーであると見なされますが、それ以外の場合は別です。
struct Outer {
struct Inner { };
};
囲むクラスの外側からは、スコープ演算子を使用してネストされたクラスにアクセスします。ただし、囲みクラスの内側からは、修飾子なしで入れ子クラスを使用できます。
struct Outer {
struct Inner { };
Inner in;
};
// ...
Outer o;
Outer::Inner i = o.in;
ネストされていないclass
/ struct
同様に、メンバー関数と静的変数は、ネストされたクラス内またはその囲む名前空間内で定義できます。ただし、ネストされたクラスとは異なるクラスと見なされるため、それらを囲むクラス内で定義することはできません。
// Bad.
struct Outer {
struct Inner {
void do_something();
};
void Inner::do_something() {}
};
// Good.
struct Outer {
struct Inner {
void do_something();
};
};
void Outer::Inner::do_something() {}
ネストされていないクラスと同様に、ネストされたクラスは、直接使用される前に定義されていれば、後で宣言して定義することができます。
class Outer {
class Inner1;
class Inner2;
class Inner1 {};
Inner1 in1;
Inner2* in2p;
public:
Outer();
~Outer();
};
class Outer::Inner2 {};
Outer::Outer() : in1(Inner1()), in2p(new Inner2) {}
Outer::~Outer() {
if (in2p) { delete in2p; }
}
C ++ 11より前では、ネストされたクラスは、囲むクラスからの型名、 static
メンバー、および列挙子にしかアクセスできませんでした。囲むクラスに定義されている他のすべてのメンバーは制限を受けませんでした。
C ++ 11では、ネストされたクラスおよびそのメンバーは、それらが囲むクラスのfriend
かのように扱われ、通常のアクセス規則に従ってすべてのメンバーにアクセスできます。ネストされたクラスのメンバーが、囲むクラスの1つ以上の非静的メンバーを評価する機能を必要とする場合は、インスタンスを渡す必要があります。
class Outer {
struct Inner {
int get_sizeof_x() {
return sizeof(x); // Legal (C++11): x is unevaluated, so no instance is required.
}
int get_x() {
return x; // Illegal: Can't access non-static member without an instance.
}
int get_x(Outer& o) {
return o.x; // Legal (C++11): As a member of Outer, Inner can access private members.
}
};
int x;
};
逆に、囲むクラスは、入れ子になったクラスのフレンドとして扱われていないので、明示的に許可を付与されることなく、そのプライベートメンバーにアクセスすることはできません。
class Outer {
class Inner {
// friend class Outer;
int x;
};
Inner in;
public:
int get_x() {
return in.x; // Error: int Outer::Inner::x is private.
// Uncomment "friend" line above to fix.
}
};
ネストされたクラスのフレンドは、自動的にそのクラスのフレンドとはみなされません。彼らが囲むクラスの友人である必要がある場合、これは別々に宣言する必要があります。逆に、囲むクラスはネストされたクラスのフレンドと自動的にはみなされないので、囲むクラスのフレンドはネストされたクラスのフレンドとはみなされません。
class Outer {
friend void barge_out(Outer& out, Inner& in);
class Inner {
friend void barge_in(Outer& out, Inner& in);
int i;
};
int o;
};
void barge_in(Outer& out, Outer::Inner& in) {
int i = in.i; // Good.
int o = out.o; // Error: int Outer::o is private.
}
void barge_out(Outer& out, Outer::Inner& in) {
int i = in.i; // Error: int Outer::Inner::i is private.
int o = out.o; // Good.
}
他のすべてのクラスメンバーと同様に、ネストされたクラスは、パブリックアクセスがある場合にのみクラスの外部から名前を付けることができます。ただし、明示的に名前を付けない限り、アクセス修飾子に関係なくアクセスできます。
class Outer {
struct Inner {
void func() { std::cout << "I have no private taboo.\n"; }
};
public:
static Inner make_Inner() { return Inner(); }
};
// ...
Outer::Inner oi; // Error: Outer::Inner is private.
auto oi = Outer::make_Inner(); // Good.
oi.func(); // Good.
Outer::make_Inner().func(); // Good.
ネストされたクラスの型エイリアスを作成することもできます。囲みクラスに型エイリアスが含まれている場合、入れ子型と型エイリアスは異なるアクセス修飾子を持つことができます。型エイリアスが囲んでいるクラスの外側にある場合は、入れ子になったクラスまたはそのtypedef
公開する必要がありtypedef
。
class Outer {
class Inner_ {};
public:
typedef Inner_ Inner;
};
typedef Outer::Inner ImOut; // Good.
typedef Outer::Inner_ ImBad; // Error.
// ...
Outer::Inner oi; // Good.
Outer::Inner_ oi; // Error.
ImOut oi; // Good.
他のクラスと同様に、ネストされたクラスは、他のクラスから派生したり、派生したりすることができます。
struct Base {};
struct Outer {
struct Inner : Base {};
};
struct Derived : Outer::Inner {};
これは、必要に応じてプログラマがネストされたクラスを更新できるようにすることで、囲むクラスが別のクラスから派生している状況で便利です。これをtypedefと組み合わせて、囲むクラスのネストされた各クラスに一貫した名前を付けることができます:
class BaseOuter {
struct BaseInner_ {
virtual void do_something() {}
virtual void do_something_else();
} b_in;
public:
typedef BaseInner_ Inner;
virtual ~BaseOuter() = default;
virtual Inner& getInner() { return b_in; }
};
void BaseOuter::BaseInner_::do_something_else() {}
// ---
class DerivedOuter : public BaseOuter {
// Note the use of the qualified typedef; BaseOuter::BaseInner_ is private.
struct DerivedInner_ : BaseOuter::Inner {
void do_something() override {}
void do_something_else() override;
} d_in;
public:
typedef DerivedInner_ Inner;
BaseOuter::Inner& getInner() override { return d_in; }
};
void DerivedOuter::DerivedInner_::do_something_else() {}
// ...
// Calls BaseOuter::BaseInner_::do_something();
BaseOuter* b = new BaseOuter;
BaseOuter::Inner& bin = b->getInner();
bin.do_something();
b->getInner().do_something();
// Calls DerivedOuter::DerivedInner_::do_something();
BaseOuter* d = new DerivedOuter;
BaseOuter::Inner& din = d->getInner();
din.do_something();
d->getInner().do_something();
上記の場合、両方BaseOuter
とDerivedOuter
メンバー型供給Inner
として、 BaseInner_
とDerivedInner_
それぞれ。これにより、ネストされた型を囲むクラスのインタフェースを壊さずに派生させることができ、ネストされた型を多態的に使用することができます。
メンバータイプとエイリアス
class
またはstruct
は、クラス自体に含まれ、そのクラス自体のメンバとして扱われる型エイリアスであるメンバ型エイリアスを定義することもできます。
struct IHaveATypedef {
typedef int MyTypedef;
};
struct IHaveATemplateTypedef {
template<typename T>
using MyTemplateTypedef = std::vector<T>;
};
静的メンバーと同様に、これらのtypedefは、スコープ演算子::
を使用してアクセスされます。
IHaveATypedef::MyTypedef i = 5; // i is an int.
IHaveATemplateTypedef::MyTemplateTypedef<int> v; // v is a std::vector<int>.
通常の型エイリアスと同様に、各メンバ型エイリアスは、その定義の前に定義されているか、または後にエイリアスされている任意の型を参照することができます。同様に、クラス定義の外にあるtypedefは、クラス定義の後ろであれば、クラス定義内のアクセス可能なtypedefを参照できます。
template<typename T>
struct Helper {
T get() const { return static_cast<T>(42); }
};
struct IHaveTypedefs {
// typedef MyTypedef NonLinearTypedef; // Error if uncommented.
typedef int MyTypedef;
typedef Helper<MyTypedef> MyTypedefHelper;
};
IHaveTypedefs::MyTypedef i; // x_i is an int.
IHaveTypedefs::MyTypedefHelper hi; // x_hi is a Helper<int>.
typedef IHaveTypedefs::MyTypedef TypedefBeFree;
TypedefBeFree ii; // ii is an int.
メンバータイプのエイリアスは、任意のアクセスレベルで宣言でき、適切なアクセス修飾子を尊重します。
class TypedefAccessLevels {
typedef int PrvInt;
protected:
typedef int ProInt;
public:
typedef int PubInt;
};
TypedefAccessLevels::PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private.
TypedefAccessLevels::ProInt pro_i; // Error: TypedefAccessLevels::ProInt is protected.
TypedefAccessLevels::PubInt pub_i; // Good.
class Derived : public TypedefAccessLevels {
PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private.
ProInt pro_i; // Good.
PubInt pub_i; // Good.
};
これは抽象レベルを提供するために使用することができ、クラスの設計者は、内部の動作をそれに依存するコードを破ることなく変更することができます。
class Something {
friend class SomeComplexType;
short s;
// ...
public:
typedef SomeComplexType MyHelper;
MyHelper get_helper() const { return MyHelper(8, s, 19.5, "shoe", false); }
// ...
};
// ...
Something s;
Something::MyHelper hlp = s.get_helper();
このような状況で、ヘルパークラスがSomeComplexType
から他の型に変更された場合、 typedef
とfriend
宣言だけを変更する必要があります。ヘルパークラスが同じ機能を提供する限り、名前で指定するのではなく、 Something::MyHelper
として使用するコードは、通常変更を加えずにそのまま動作します。このようにして、基本的な実装が変更されたときに変更する必要があるコードの量を最小限に抑え、型名を1箇所で変更するだけです。
decltype
と組み合わせることもできます。
class SomethingElse {
AnotherComplexType<bool, int, SomeThirdClass> helper;
public:
typedef decltype(helper) MyHelper;
private:
InternalVariable<MyHelper> ivh;
// ...
public:
MyHelper& get_helper() const { return helper; }
// ...
};
このような状況では、 SomethingElse::helper
実装を変更すると、 decltype
ために自動的にtypedefが変更されdecltype
。これにより、 helper
を変更したいときに必要な変更が最小限に抑えられ、人的ミスのリスクが最小限に抑えられます。
しかし、すべてと同様に、これはあまりにも遠くに取られる可能性があります。タイプ名が内部的に1回または2回だけ使用され、外部的に0回使用される場合などは、別名を指定する必要はありません。プロジェクト全体で数百回または数千回使用されている場合、または名前が十分に長い場合は、絶対に絶対に使用するのではなく、typedefとして指定すると便利です。フォワードの互換性と利便性のバランスを、不必要なノイズの発生量と合わせなければなりません。
これは、クラス外でのテンプレートパラメータへのアクセスを提供するために、テンプレートクラスでも使用できます。
template<typename T>
class SomeClass {
// ...
public:
typedef T MyParam;
MyParam getParam() { return static_cast<T>(42); }
};
template<typename T>
typename T::MyParam some_func(T& t) {
return t.getParam();
}
SomeClass<int> si;
int i = some_func(si);
これは通常、コンテナで使用されます。コンテナは、通常、要素型と他のヘルパー型をメンバー型エイリアスとして提供します。たとえば、C ++標準ライブラリのほとんどのコンテナは、必要な他の特別な型とともに、次の12種類のヘルパー型を提供します。
template<typename T>
class SomeContainer {
// ...
public:
// Let's provide the same helper types as most standard containers.
typedef T value_type;
typedef std::allocator<value_type> allocator_type;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef MyIterator<value_type> iterator;
typedef MyConstIterator<value_type> const_iterator;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
};
C ++ 11より前のバージョンでは、この機能はまだ利用できなかったため、ソートの「テンプレート型typedef
」を提供するためによく使用されていました。これらはエイリアステンプレートの導入で少し一般的になっていますが、いくつかの状況ではまだ有用です(そして、別の状況ではエイリアステンプレートと組み合わされています。複雑な型の個々のコンポーネント、 )。彼らは通常、自分のタイプエイリアスに名前type
を使用します。
template<typename T>
struct TemplateTypedef {
typedef T type;
}
TemplateTypedef<int>::type i; // i is an int.
これは、1つ以上のパラメータを定義するエイリアスを提供するために、複数のテンプレートパラメータを持つ型でよく使用されていました。
template<typename T, size_t SZ, size_t D>
class Array { /* ... */ };
template<typename T, size_t SZ>
struct OneDArray {
typedef Array<T, SZ, 1> type;
};
template<typename T, size_t SZ>
struct TwoDArray {
typedef Array<T, SZ, 2> type;
};
template<typename T>
struct MonoDisplayLine {
typedef Array<T, 80, 1> type;
};
OneDArray<int, 3>::type arr1i; // arr1i is an Array<int, 3, 1>.
TwoDArray<short, 5>::type arr2s; // arr2s is an Array<short, 5, 2>.
MonoDisplayLine<char>::type arr3c; // arr3c is an Array<char, 80, 1>.
静的クラスのメンバー
クラスには、変数または関数のいずれかのstatic
メンバーも含めることができます。これらはクラスのスコープにあると見なされますが、通常のメンバーとしては扱われません。それらは静的な記憶期間(プログラムの始めから終わりまで存在します)を持ち、クラスの特定のインスタンスに結びついておらず、クラス全体で1つのコピーしか存在しません。
class Example {
static int num_instances; // Static data member (static member variable).
int i; // Non-static member variable.
public:
static std::string static_str; // Static data member (static member variable).
static int static_func(); // Static member function.
// Non-static member functions can modify static member variables.
Example() { ++num_instances; }
void set_str(const std::string& str);
};
int Example::num_instances;
std::string Example::static_str = "Hello.";
// ...
Example one, two, three;
// Each Example has its own "i", such that:
// (&one.i != &two.i)
// (&one.i != &three.i)
// (&two.i != &three.i).
// All three Examples share "num_instances", such that:
// (&one.num_instances == &two.num_instances)
// (&one.num_instances == &three.num_instances)
// (&two.num_instances == &three.num_instances)
静的メンバー変数は、クラス内で定義されているとはみなされず、宣言されているだけであるため、クラス定義の外に定義されます。プログラマは、定義内の静的変数を初期化することはできますが、必須ではありません。メンバ変数を定義するとき、キーワードstatic
は省略されます。
class Example {
static int num_instances; // Declaration.
public:
static std::string static_str; // Declaration.
// ...
};
int Example::num_instances; // Definition. Zero-initialised.
std::string Example::static_str = "Hello."; // Definition.
このため、静的変数は、完全な型として後で定義される限り、( void
除いて)不完全な型になる可能性があります。
struct ForwardDeclared;
class ExIncomplete {
static ForwardDeclared fd;
static ExIncomplete i_contain_myself;
static int an_array[];
};
struct ForwardDeclared {};
ForwardDeclared ExIncomplete::fd;
ExIncomplete ExIncomplete::i_contain_myself;
int ExIncomplete::an_array[5];
静的メンバ関数は、通常のメンバ関数と同様に、クラス定義の内部または外部で定義することができます。静的メンバ変数の場合と同様に、静的メンバ関数をクラス定義の外に定義する場合、キーワードstatic
は省略されます。
// For Example above, either...
class Example {
// ...
public:
static int static_func() { return num_instances; }
// ...
void set_str(const std::string& str) { static_str = str; }
};
// Or...
class Example { /* ... */ };
int Example::static_func() { return num_instances; }
void Example::set_str(const std::string& str) { static_str = str; }
静的メンバー変数がconst
で宣言されていてvolatile
ではなく、整数型または列挙型である場合、クラス定義内の宣言で初期化することができます。
enum E { VAL = 5 };
struct ExConst {
const static int ci = 5; // Good.
static const E ce = VAL; // Good.
const static double cd = 5; // Error.
static const volatile int cvi = 5; // Error.
const static double good_cd;
static const volatile int good_cvi;
};
const double ExConst::good_cd = 5; // Good.
const volatile int ExConst::good_cvi = 5; // Good.
C ++ 11では、 LiteralType
型の静的メンバ変数( constexpr
規則に従ってコンパイル時に構築できる型)もconstexpr
として宣言できます。その場合は、クラス定義内で初期化する必要があります。
struct ExConstexpr {
constexpr static int ci = 5; // Good.
static constexpr double cd = 5; // Good.
constexpr static int carr[] = { 1, 1, 2 }; // Good.
static constexpr ConstexprConstructibleClass c{}; // Good.
constexpr static int bad_ci; // Error.
};
constexpr int ExConstexpr::bad_ci = 5; // Still an error.
const
またはconstexpr
静的メンバ変数がodr-used (アドレスが取られているか、または参照に割り当てられている場合、非公式に)、クラス定義の外に別の定義が残っている必要があります。この定義には初期化子を含めることはできません。
struct ExODR {
static const int odr_used = 5;
};
// const int ExODR::odr_used;
const int* odr_user = & ExODR::odr_used; // Error; uncomment above line to resolve.
静的メンバーは特定のインスタンスに結び付けられていないため、スコープ演算子::
を使用してアクセスできます。
std::string str = Example::static_str;
静的でない通常のメンバーであるかのようにアクセスすることもできます。これは歴史的に重要な意味を持ちますが、メンバーが静的であるか非静的であるかの混乱を避けるために、スコープ演算子よりもあまり一般的ではありません。
Example ex;
std::string rts = ex.static_str;
クラスメンバは、非静的クラスメンバの場合と同様に、スコープを修飾せずに静的メンバにアクセスできます。
class ExTwo {
static int num_instances;
int my_num;
public:
ExTwo() : my_num(num_instances++) {}
static int get_total_instances() { return num_instances; }
int get_instance_number() const { return my_num; }
};
int ExTwo::num_instances;
彼らはmutable
することはできませんし、彼らがする必要があります。特定のインスタンスに関連付けられていないため、インスタンスがconstであるかどうかにかかわらず、静的メンバーには影響しません。
struct ExDontNeedMutable {
int immuta;
mutable int muta;
static int i;
ExDontNeedMutable() : immuta(-5), muta(-5) {}
};
int ExDontNeedMutable::i;
// ...
const ExDontNeedMutable dnm;
dnm.immuta = 5; // Error: Can't modify read-only object.
dnm.muta = 5; // Good. Mutable fields of const objects can be written.
dnm.i = 5; // Good. Static members can be written regardless of an instance's const-ness.
静的メンバーは、非静的メンバーと同様にアクセス修飾子を尊重します。
class ExAccess {
static int prv_int;
protected:
static int pro_int;
public:
static int pub_int;
};
int ExAccess::prv_int;
int ExAccess::pro_int;
int ExAccess::pub_int;
// ...
int x1 = ExAccess::prv_int; // Error: int ExAccess::prv_int is private.
int x2 = ExAccess::pro_int; // Error: int ExAccess::pro_int is protected.
int x3 = ExAccess::pub_int; // Good.
与えられたインスタンスに束縛されていないので、静的メンバー関数にはthis
ポインタはありません。このため、インスタンスを渡さない限り、非静的メンバー変数にアクセスすることはできません。
class ExInstanceRequired {
int i;
public:
ExInstanceRequired() : i(0) {}
static void bad_mutate() { ++i *= 5; } // Error.
static void good_mutate(ExInstanceRequired& e) { ++e.i *= 5; } // Good.
};
this
ポインタを持たないため、そのアドレスはメンバ関数へのポインタに格納することはできず、代わりに通常のポインタへのポインタに格納されます。
struct ExPointer {
void nsfunc() {}
static void sfunc() {}
};
typedef void (ExPointer::* mem_f_ptr)();
typedef void (*f_ptr)();
mem_f_ptr p_sf = &ExPointer::sfunc; // Error.
f_ptr p_sf = &ExPointer::sfunc; // Good.
this
ポインタを持たないため、 const
やvolatile
でもconst
ませんし、ref-qualifierを持つこともできません。彼らはまた、仮想にすることはできません。
struct ExCVQualifiersAndVirtual {
static void func() {} // Good.
static void cfunc() const {} // Error.
static void vfunc() volatile {} // Error.
static void cvfunc() const volatile {} // Error.
static void rfunc() & {} // Error.
static void rvfunc() && {} // Error.
virtual static void vsfunc() {} // Error.
static virtual void svfunc() {} // Error.
};
与えられたインスタンスに結び付けられていないので、静的メンバー変数は効果的に特殊なグローバル変数として扱われます。プログラムの開始時に作成され、クラスのインスタンスが実際に存在するかどうかに関係なく、終了時に破棄されます。変数がthread_local
(C ++ 11以降)として宣言されている場合を除き、各静的メンバ変数のコピーが1つだけ存在します(この場合、スレッドごとに1つのコピーがあります)。
静的メンバー変数には、クラスに外部リンクまたは内部リンクがあるかどうかにかかわらず、クラスと同じリンケージがあります。ローカルクラスおよび名前のないクラスは、静的メンバーを持つことはできません。
非静的メンバー関数
クラスには、クラスの個々のインスタンスで動作する非静的メンバ関数を持たせることができます。
class CL {
public:
void member_function() {}
};
これらの関数は、次のように、クラスのインスタンスで呼び出されます。
CL instance;
instance.member_function();
クラス定義の内部または外部で定義できます。外部で定義されている場合は、クラスのスコープ内に指定されます。
struct ST {
void defined_inside() {}
void defined_outside();
};
void ST::defined_outside() {}
彼らはすることができCV修飾および/またはREF-資格 、彼らは彼らは時に呼び出されているインスタンスを参照してくださいどのように影響を与えます。関数はインスタンスが指定されたcv-qualifierを持つものと見なします(存在する場合)。呼び出されるバージョンは、インスタンスのcv修飾子に基づいています。インスタンスと同じcv-qualifierを持つバージョンがない場合、利用可能であれば、より多くのcv修飾バージョンが呼び出されます。
struct CVQualifiers {
void func() {} // 1: Instance is non-cv-qualified.
void func() const {} // 2: Instance is const.
void cv_only() const volatile {}
};
CVQualifiers non_cv_instance;
const CVQualifiers c_instance;
non_cv_instance.func(); // Calls #1.
c_instance.func(); // Calls #2.
non_cv_instance.cv_only(); // Calls const volatile version.
c_instance.cv_only(); // Calls const volatile version.
メンバ関数のref修飾子は、関数がrvalueインスタンスで呼び出されることを意図しているかどうかを示し、関数cv-qualifiersと同じ構文を使用します。
struct RefQualifiers {
void func() & {} // 1: Called on normal instances.
void func() && {} // 2: Called on rvalue (temporary) instances.
};
RefQualifiers rf;
rf.func(); // Calls #1.
RefQualifiers{}.func(); // Calls #2.
CV修飾子とref修飾子は、必要に応じて組み合わせることもできます。
struct BothCVAndRef {
void func() const& {} // Called on normal instances. Sees instance as const.
void func() && {} // Called on temporary instances.
};
それらは仮想でもあります 。これは多態性の基本であり、子クラスが独自の機能を提供しながら、親クラスと同じインターフェースを提供することを可能にします。
struct Base {
virtual void func() {}
};
struct Derived {
virtual void func() {}
};
Base* bp = new Base;
Base* dp = new Derived;
bp.func(); // Calls Base::func().
dp.func(); // Calls Derived::func().
無名の構造体/クラス
無名のstruct
が許可されています(型に名前がありません)
void foo()
{
struct /* No name */ {
float x;
float y;
} point;
point.x = 42;
}
または
struct Circle
{
struct /* No name */ {
float x;
float y;
} center; // but a member name
float radius;
};
以降
Circle circle;
circle.center.x = 42.f;
匿名のstruct
ません(名前のない型および名前のないオブジェクト)
struct InvalidCircle
{
struct /* No name */ {
float centerX;
float centerY;
}; // No member either.
float radius;
};
注意:一部のコンパイラでは、 匿名のstruct
を拡張子として使用できます。
lamdbaは特別な名前のない
struct
と見ることができます。decltype
は無名のstruct
の型を取り出すことができます:decltype(circle.point) otherPoint;
名前のない
struct
インスタンスは、テンプレートメソッドのパラメータになります。void print_square_coordinates() { const struct {float x; float y;} points[] = { {-1, -1}, {-1, 1}, {1, -1}, {1, 1} }; // for range relies on `template <class T, std::size_t N> std::begin(T (&)[N])` for (const auto& point : points) { std::cout << "{" << point.x << ", " << point.y << "}\n"; } decltype(points[0]) topRightCorner{1, 1}; auto it = std::find(points, points + 4, topRightCorner); std::cout << "top right corner is the " << 1 + std::distance(points, it) << "th\n"; }