1. コンストラクターの概要
C++ では、コンストラクターは、クラスの新しいオブジェクトが作成されるときに自動的に呼び出される特別なメンバー関数です。コンストラクターの主な目的は、クラスのオブジェクトを初期化することです。
コンストラクターに関する重要な情報を次に示します。
1. コンストラクターの名前: コンストラクターの名前はクラス名とまったく同じです。
2. 自動呼び出し: コンストラクターを明示的に呼び出す必要はありません。オブジェクトの作成時に自動的に呼び出されます。同様に、デストラクター (~
クラス名の前にチルダが付いた関数) は、オブジェクトがその有効期間を超えると自動的に呼び出されます。
3. 戻り値の型がない: コンストラクターには戻り値の型さえありvoid
ません。
4. オーバーロード可能: 他の関数と同様に、パラメーターの数または型が異なる限り、コンストラクターもオーバーロードできます。
5. デフォルトパラメータが存在する可能性があります: コンストラクタはデフォルトパラメータを持つことができます。引数が指定されていない場合は、これらのデフォルト値が使用されます。
6. 型コンストラクター: コンストラクターにパラメータが 1 つだけある場合は、暗黙的な変換にも使用できます。
例えば:
class Rectangle {
public:
int width;
int height;
// 这是一个构造函数
Rectangle(int w, int h) {
width = w;
height = h;
}
};
// 使用构造函数创建 Rectangle 对象
Rectangle rect(10, 5);
この例では、Rectangle
クラスには 2 つのパラメーターw
と を受け取りh
、それらのパラメーターの値をメンバー変数width
とにそれぞれ割り当てるコンストラクターがありますheight
。次に、Rectangle
objectを作成するときにrect
、コンストラクターを使用して、width
と の値を提供しますheight
。
2. コンストラクター: 型コンストラクター
型コンストラクター(変換コンストラクターとも呼ばれる) は、引数を 1 つだけ取る特別な型のコンストラクターです。型コンストラクターを使用すると、オブジェクトが初期化または割り当てられるときに、オブジェクトをそのクラスの型に暗黙的に変換できます。この変換コンストラクターは、特定の型からクラス型への変換メソッドを定義します。
たとえば、Fraction
分子と分母で分数を表すクラスが呼び出されたとします。Fraction
分子がこの整数、分母が 1 である場合、integer から直接作成できるようにしたい場合があります。これを行うには、パラメーターを受け入れるint
コンストラクターを定義します。
class Fraction {
private:
int numerator;
int denominator;
public:
// 类型构造函数
Fraction(int num) : numerator(num), denominator(1) {}
// 通常的构造函数
Fraction(int num, int denom) : numerator(num), denominator(denom) {}
// 其他成员函数...
};
Fraction
この型コンストラクターを配置すると、次のようにオブジェクトを初期化できます。
Fraction frac1 = 5; // 使用类型构造函数,等价于 Fraction frac1(5);
Fraction frac2(7, 2); // 使用通常的构造函数
上記のコードでは、frac1
整数で5
初期化されています。コンパイラは、型コンストラクターを使用して、この整数を暗黙的に型に変換しますFraction
。
この暗黙的な変換は予期しない動作を引き起こす可能性があることに注意してください。そのため、コンストラクターに暗黙的な変換を実行させたくない場合は、コンストラクターの前にキーワードを追加できます。これには、明示的な Converts を明示的なコンストラクターに要求します。例えば: explicit
class Fraction {
private:
int numerator;
int denominator;
public:
// 显式类型构造函数
explicit Fraction(int num) : numerator(num), denominator(1) {}
// 通常的构造函数
Fraction(int num, int denom) : numerator(num), denominator(denom) {}
// 其他成员函数...
};
この例では、暗黙的な変換が防止されているため、次のコードは許可されなくなります。
Fraction frac1 = 5; // 编译错误:不能隐式转换
Fraction
整数からオブジェクトを作成したい場合は、コンストラクターを明示的に呼び出す必要があります。
Fraction frac1(5); // OK:显式地调用构造函数
3. コンストラクター: コピーコンストラクター
C++ では、コピー コンストラクターは、オブジェクトの新しいコピーを作成するために使用される特別なコンストラクターです。コピー コンストラクターは、同じ型のオブジェクトへの参照をパラメーターとして受け入れ、このオブジェクトの値をコピーします。
コピー コンストラクターに関する重要な情報を次に示します。
-
コピー コンストラクターの宣言: コピー コンストラクターの一般的な形式は です
ClassName(const ClassName& obj)
。ここで、ClassName
はクラスの名前、 はobj
渡された同じ型のオブジェクトへの参照です。 -
いつ呼び出すか: コピー コンストラクターは、新しいオブジェクトが既存のオブジェクトのコピーとして作成されるとき、オブジェクトが関数のパラメーターとして値によって渡されるとき、関数がオブジェクトを返すとき、次の状況で呼び出されます。
-
デフォルトのコピー コンストラクター: クラスのコピー コンストラクターを定義しない場合、コンパイラーはデフォルトのコピー コンストラクターを自動的に生成します。デフォルトのコピー コンストラクターは、すべてのメンバー変数の浅いコピーを実行します。クラスのメンバーにポインターまたは動的に割り当てられたメモリーが含まれている場合、ディープ コピーを実行するには独自のコピー コンストラクターを定義する必要がある場合があります。
以下は、コピー コンストラクターを使用した単純なクラスの例です。
class MyClass {
private:
int* data;
public:
// 构造函数,用于初始化动态分配的内存
MyClass(int size) {
data = new int[size];
// 初始化数据...
}
// 拷贝构造函数
MyClass(const MyClass& other) {
// 这是一个简单的例子,实际情况可能更复杂
// 你可能需要复制 'other.data' 中的实际数据
data = new int[sizeof(other.data)];
memcpy(data, other.data, sizeof(other.data));
}
// 析构函数,用于释放动态分配的内存
~MyClass() {
delete[] data;
}
// 其他成员函数...
};
この例では、動的に割り当てられた配列メンバーがMyClass
あります。このクラスのコピー コンストラクターは、新しい配列を作成し、元のオブジェクトのメンバーの内容を新しい配列にコピーします。これは、ポインターだけでなくデータ自体をコピーするため、ディープ コピーと呼ばれます。int
data
int
data
クラスに動的に割り当てられたメモリや特別な処理が必要なその他のリソースがある場合は、これらのリソースが正しくコピーされるように独自のコピー コンストラクターを定義する必要がある場合があることに注意してください。それ以外の場合、コンパイラによって自動的に生成されるデフォルトのコピー コンストラクターに依存すると、浅いコピーしか作成されない可能性があります。これにより、メモリ リークやダングリング ポインタなどの問題が発生する可能性があります。
ディープ コピーとシャロー コピー: C++ では、ディープ コピーとシャロー コピーは 2 つの異なるオブジェクト コピー メソッドであり、主な違いはオブジェクトのポインター メンバーの処理方法です。
浅いコピー: 浅いコピーを実行すると、オブジェクトのすべての非静的メンバー変数のみが新しいオブジェクトにコピーされます。メンバー変数にポインターが含まれている場合、ポインターが指すものではなく、ポインター値 (つまり、アドレス) がコピーされます。これは、元のオブジェクトとコピーされたオブジェクトが同じ動的メモリを共有することを意味します。これにより、問題が発生する可能性があります。たとえば、オブジェクトが削除されると、そのデストラクタが共有メモリを削除し、別のオブジェクトのポインタがダングリング ポインタになる可能性があります。
ディープ コピー: ディープ コピーを実行すると、オブジェクトのすべての非静的メンバー変数をコピーするだけでなく、動的メモリ割り当てのポインタ メンバーごとに新しいメモリ コピーも作成され、新しいオブジェクトが確実に新しいコピーを取得します。同じメモリを参照するのではなく、元のオブジェクト データをそのまま使用します。このようにして、2 つのオブジェクトはメモリを共有せず、浅いコピーで発生する可能性のある問題を回避します。
クラスのメンバー変数にプリミティブ型または他の値意味型 ( など) のみが含まれている場合は、通常、コンパイラーによって生成されたデフォルトのコピー コンストラクターを利用して浅いコピーを実行できますstd::string
。std::vector
ただし、クラスに動的に割り当てられたメモリへのポインタ メンバーが含まれている場合、または特別な処理が必要なリソース (ファイル ハンドル、ネットワーク接続など) が含まれている場合は、通常、ディープ コピーを実行するためのカスタム コピー コンストラクターを提供する必要があります。そうしないと、メモリ リーク、ダングリング ポインタなどの問題が発生する可能性があります。
4番目、デストラクター
C++ では、デストラクターは、オブジェクトの存続期間の終わりにオブジェクトをクリーンアップする特別なメンバー関数です。オブジェクトが定義されているスコープを離れたか、動的に割り当てられたメモリが破棄されたため、オブジェクトが破棄されようとしている場合、delete
そのデストラクターが呼び出されます。
デストラクターに関する重要な情報を次に示します。
-
デストラクターの名前: デストラクターの名前はクラス名と同じですが、先頭にチルダが付きます
~
。 -
自動呼び出し: デストラクターを明示的に呼び出す必要はありません。オブジェクトが破棄されると自動的に呼び出されます。
-
戻り値の型もパラメータもありません: デストラクターには戻り値の型もパラメータもありません。これは、デストラクターをオーバーロードできないことを意味します。
-
目的: デストラクターは、オブジェクトが所有するリソースを解放するためによく使用されます。たとえば、オブジェクトに動的に割り当てられたメモリへのポインタ メンバがある場合、デストラクタはそのメモリを削除する必要がある場合があります。そうしないと、メモリ リークが発生する可能性があります。
デストラクターを使用した単純なクラスの例を次に示します。
class MyClass {
private:
int* data;
public:
// 构造函数,用于初始化动态分配的内存
MyClass(int size) {
data = new int[size];
// 初始化数据...
}
// 析构函数,用于释放动态分配的内存
~MyClass() {
delete[] data;
}
// 其他成员函数...
};
この例では、動的に割り当てられた配列メンバーがMyClass
あります。オブジェクトが破棄されると、そのデストラクターによって配列が削除され、メモリ リークが防止されます。int
data
MyClass
クラスに継承関係がある場合、通常はデストラクターを として宣言する必要があることに注意してくださいvirtual
。こうすることで、派生クラス オブジェクトへの基本クラス ポインターが削除されるときに、正しいデストラクターが呼び出されます。デストラクターが でない場合virtual
、おそらく基本クラスのデストラクターのみが呼び出され、その結果、派生クラスのリソースが適切にクリーンアップされなくなります。
5、コンストラクターとデストラクターの呼び出しシーケンス
コンストラクターとデストラクターが呼び出される順序は C++ で明確に定義されており、オブジェクトの作成と破棄に密接に関連しています。
1. コンストラクターの呼び出し順序:
-
まず、基本クラスのコンストラクターが呼び出されます。
-
次に、派生クラスのメンバー変数のコンストラクターが、クラス定義に出現する順序で呼び出されます。
-
最後に、派生クラスのコンストラクターが呼び出されます。
2. デストラクタの呼び出し順序:
デストラクターが呼び出される順序は、コンストラクターが呼び出される順序とはまったく逆です。
-
まず、派生クラスのデストラクターが呼び出されます。
-
派生クラスのメンバー変数のデストラクターは、クラス定義に出現する順序とは逆の順序で呼び出されます。
-
最後に、基本クラスのデストラクターが呼び出されます。
この順序により、オブジェクトの存続期間全体にわたってリソースの取得と解放が正確かつ効率的に行われることが保証されます。構築フェーズでは、まず基本クラスとメンバー変数を構築し、次に派生クラスを構築します。デストラクター フェーズでは、派生クラスが最初に破棄され、次にメンバー変数と基底クラスが破棄され、派生クラスのデストラクターでメンバー変数または基底クラスに引き続きアクセスする必要がある状況を回避します。
この順序を説明する簡単な例を次に示します。
#include <iostream>
class Base {
public:
Base() { std::cout << "Base Constructor\n"; }
virtual ~Base() { std::cout << "Base Destructor\n"; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived Constructor\n"; }
~Derived() { std::cout << "Derived Destructor\n"; }
};
int main() {
Derived d;
return 0;
}
このコードの出力は次のようになります。
Base Constructor
Derived Constructor
Derived Destructor
Base Destructor
この出力は、コンストラクターとデストラクターが呼び出される順序を明確に示しています。