高度なコンストラクタ
[第]28[課では]コンストラクタ[の基礎]を学びました。
[実際の場面では]、[objectをコピー]、[objectを移動]、[初期化リストを使って効率を上げる]……といった操作が必要になるかもしれません。
[このレッスン]では、[コンストラクタの][応用的な使い方]を[学びましょう]。
1. デフォルトコンストラクタ
(1) 1.1 デフォルトコンストラクタとは?
デフォルトコンストラクタ[とは][引数なし][の]コンストラクタのこと。
#include iostream
#include string
class Student {
private:
std::string name;
int age;
public:
// ()
Student() {
name = "Unknown";
age = 0;
std::cout << "..." << std::endl;
}
};
int main() {
Student s1; // ✅ callingconstructor
return 0;
}
💡 [要点]: [もし][コンストラクタを一切記述していない]場合、コンパイラは[自動的に]デフォルトコンストラクタを生成します([何も行いません])。[しかし、もし][いずれかの]コンストラクタを記述している]場合、コンパイラは[デフォルトコンストラクタを][自動的に生成しなくなります]。
(2) 1.2 [問題]:[引数付き]コンストラクタ[を記述した後]、[デフォルトでのコンストラクタ呼び出しができなくなる]
#include iostream
#include string
class Student {
private:
std::string name;
int age;
public:
// ...
Student(const std::string& n, int a) {
name = n;
age = a;
}
};
int main() {
Student s1("Alice", 20); // ✅
Student s2; // ❌:Yesconstructor
return 0;
}
[修正]: [明示的に]デフォルトコンストラクタを記述するか、[または] = default(C++11)を使用する。
//:
Student() {
name = "Unknown";
age = 0;
}
//: = default(C++11,)
Student() = default;
2. [リスト]の初期化(Initializer List)
(1) 2.1 [リスト]の初期化とは何か?
[初期化リスト]は、[コンストラクタの][コロン][の直後に][メンバー変数を直接初期化する]ものであり、[関数本体][内で値を代入する]ものではありません。
[従来の表記法]([賦]value):
Student(const std::string& n, int a) {
name = n; //,
age = a;
}
[初期化リストの書き方]([推奨]):
Student(const std::string& n, int a) : name(n), age(a) {
// ...
}
💡 [強み]:
- [効率が高い]([直接初期化]、[デフォルトのコンストラクタで生成してから値を代入するのではなく])
- [初期化リストを使用しなければならない場面]:
constメンバー([初期化時に値を割り当てる必要がある])- referencemember([初期化必須])
- [デフォルトコンストラクタがない]メンバクラス
(2) 2.2 [必須]リストの初期化 [シーン]
[シーン1]:constのメンバー
#include iostream
#include string
class Student {
private:
const std::string id; // const
std::string name;
public:
// ❌:const value
// Student(const std::string& i, const std::string& n) {
// id = i; // ❌ const value
// name = n;
// }
// ✅:Usinglist
Student(const std::string& i, const std::string& n) : id(i), name(n) {
}
};
int main() {
Student s1("20240001", "Alice");
return 0;
}
3. コピーコンストラクタ(Copy Constructor)
(1) 3.1 コピーコンストラクタとは何か?
コピーコンストラクタとは、[ある]オブジェクト[を別の]オブジェクト[で初期化する]ためのコンストラクタです。
[トリガーシーン]:
- [ある]オブジェクト[を使って別の]オブジェクトを初期化する
- 関数[引数]は値渡し([コピーされる])
- 関数[戻り値]オブジェクト([コピー可能])
▶ サンプル 1: [デフォルトの]コピーコンストラクタ (難易度 ⭐)
#include iostream
#include string
class Student {
public:
std::string name;
int age;
// ()
};
int main() {
Student s1;
s1.name = "Alice";
s1.age = 20;
Student s2 = s1; // ✅ callingcopy constructor
std::cout << "s2:" << s2.name << std::endl; // Alice
std::cout << "s2:" << s2.age << std::endl; // 20
return 0;
}
💡 [要点]: [もし][コピーコンストラクタを定義していない場合]、コンパイラは[自動的に]([メンバーごとに]コピー)を生成します。
(2) 3.2 [カスタム]コピーコンストラクタ
#include iostream
#include string
class Student {
private:
std::string name;
int age;
public:
// ...
Student(const std::string& n, int a) : name(n), age(a) {
std::cout << ":" << name << std::endl;
}
// ...
Student(const Student& other) : name(other.name), age(other.age) {
std::cout << ":" << name << std::endl;
}
};
int main() {
Student s1("Alice", 20);
Student s2 = s1; // ...
return 0;
}
出力:
4. [浅いコピー] 対 [深いコピー]
(1) 4.1 [問題]:[デフォルトのコピーは]「[浅いコピー]」
[もし]クラス[に]ポインタメンバ([ヒープメモリを]指す)がある場合、[デフォルトの]コピーコンストラクタは[単に][ポインタの]値(アドレス)を[コピー]するだけで、[ポインタが指す内容]は[コピー]しません。
#include iostream
#include cstring
class MyClass {
public:
char* data;
MyClass() {
data = new char[5];
std::strcpy(data, "Hello");
}
// ❌ YesDefinecopy constructor(Using's)
~MyClass() {
delete data;
}
};
int main() {
MyClass obj1;
MyClass obj2 = obj1; //:obj1.data obj2.data
// main,obj2 ( data)
// obj1 ( data,)→!
return 0;
}
💡 [これが]「[浅いコピー]」の問題です――[2つの]オブジェクトの[ポインタが][同じ]メモリ領域を[指しているため]、[破棄時に][重複して解放]されてしまいます。
(2) 4.2 [解決策]:[カスタムディープコピー]
#include iostream
#include cstring
class MyClass {
public:
char* data;
MyClass() {
data = new char[5];
std::strcpy(data, "Hello");
}
// ()
MyClass(const MyClass& other) {
data = new char[std::strlen(other.data) + 1];
std::strcpy(data, other.data);
}
~MyClass() {
delete data;
}
};
int main() {
MyClass obj1;
MyClass obj2 = obj1; //:obj2.data points
std::cout << "obj1.data = " << obj1.data << std::endl;
std::cout << "obj2.data = " << obj2.data << std::endl;
return 0;
}
💡 [重要]: [もし]クラス[に]ポインタ型のメンバがある場合、[コピーコンストラクタを定義する必要がある]([ディープコピーを実装する])。
5. 移動コンストラクタ(C++11、[選択科目])
(1) 5.1 移動コンストラクタとは何か?
ムーブコンストラクタ[は] C++11 [で導入された新機能]――「[他人のリソースを]」[コピーするのではなく]。
[シナリオ]: [もし][一時的な]オブジェクト([まもなく破棄される]もの)がある場合、[その内容をコピーするのは無駄]だ――[それなら]「[盗む]」方がよい。
#include iostream
#include string
class MyClass {
private:
std::string* data;
public:
// ...
MyClass(const std::string& s) {
data = new std::string(s);
std::cout << "..." << std::endl;
}
// (C++11)
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr; // other ()
std::cout << "..." << std::endl;
}
~MyClass() {
delete data;
}
};
int main() {
MyClass obj1("Hello");
MyClass obj2 = std::move(obj1); // ...
return 0;
}
💡 [ヒント]: ムーブコンストラクタは[高度なトピック]です。[初心者はまずは概要を把握し]、[後で詳しく学ぶ]ことをお勧めします。
6. 演習:[完全版] Student クラス
▶ サンプル 2: [コピーコンストラクタを備えた] Student クラス (難易度 ⭐⭐)
#include iostream
#include string
class Student {
private:
std::string name;
int age;
public:
// ...
Student(const std::string& n, int a) : name(n), age(a) {
std::cout << ":" << name << std::endl;
}
// ...
Student(const Student& other) : name(other.name), age(other.age) {
std::cout << ":" << name << std::endl;
}
// ...
~Student() {
std::cout << ":" << name << std::endl;
}
void introduce() {
std::cout << "My name is" << name << ",I'm" << age << "years old。" << std::endl;
}
};
int main() {
Student s1("Alice", 20);
Student s2 = s1; // ...
s1.introduce();
s2.introduce();
return 0;
}
❓ よくある質問
Q:[どのような場合に]コピーコンストラクタを定義する必要があるか? A:[クラス内に]([ヒープメモリを]指す)ポインタ型のメンバがある[場合]。
クラスに[基本型](
int、double)[または] STL型(std::string、std::vector)のみが含まれている場合、[独自に実装する必要はない]――コンパイラが[自動的に生成する]コピーコンストラクタが[正しく動作する](STL型は[ディープコピーを独自に実装している])。
Q:[リストの初期化と]関数本体での[値の代入]では、[効率にどれほどの差がある]か? A:[基本]型の場合、[それほど差はない]。[しかし] STL型(
std::string、std::vector)の場合、[大きな差がある]――
- 関数本体:[最初に空の]オブジェクトを初期化し、[その後]valueを代入する([必要に応じて]メモリを割り当て、[内容をコピーする])。
- [初期化リスト]:[直接初期化]([1回のみ割り当て]メモリ)
[提案]: [一律に][初期化リスト]を使用する。
Q:移動コンストラクタ[は何に役立つ]? A:[一時的な]オブジェクトの[コピー効率を向上させる]。
[例えば]関数が[大きな]オブジェクトを[返す]場合、moveコンストラクタ[を使用することで][ディープコピー]([直接]一時的なオブジェクトのメモリを「盗む」)を[回避できる]。
📖 まとめ
- デフォルトコンストラクタ[引数なしの]コンストラクタ
- [初期化リスト][効率が向上]し、[さらに]
constメンバーや参照メンバーも処理可能 - コピーコンストラクタ[ある]オブジェクト[を使って別の]オブジェクトを初期化するために使用される
- [もし]クラス[に]ポインタ型のメンバがある場合、[深コピーを独自に実装する必要がある]
- 移動コンストラクタ(C++11)[一時的なオブジェクトのコピー効率を向上させることができる]
📝 練習問題
-
初心者(難易度 ⭐):
Bookというクラスを定義し、[書籍名]と[著者2名]のメンバを含める。[初期化リスト][を使用して]コンストラクタを記述する。 -
中級(難易度 ⭐⭐): [カスタム]
Bookクラスのコピーコンストラクタ、[ディープコピーを実装]([書籍名が]char*[で保存されている] と仮定し、[新しい]メモリを割り当てる必要がある)。 -
上級(難易度 ⭐⭐⭐): [定義する]
DynamicArrayクラス([動的]配列)、[以下を含む]:
int* data([対象]heapmemory)int size(array[大小])
[実装]:
- [普通]コンストラクタ([割り当て]メモリ)
- コピーコンストラクタ([ディープコピー])
- デストラクタ([メモリの解放])
[テスト]:[作成] DynamicArray obj1(10)、[再利用] obj1、[初期化] obj2、[2つの]object[が] data [異なる]memoryaddressを指していることを検証する。
- デフォルトコンストラクタ:[引数なしまたはすべての引数がデフォルト値]
- [初期化リストは]コンストラクタ[内部での代入]よりも[効率的]である
- [委譲]コンストラクタ:[ある]コンストラクタ[が別の]コンストラクタを呼び出す
- 明示的 [暗黙的を防止] 型 [変換]
- デストラクタ:オブジェクト[破棄時に自動的に呼び出される]、[リソースを解放する]
7. 🚀 次は
[コンストラクタの]高度な使い方を[習得しました]。[次は] 演算子のオーバーロード([第]30[回])を学びます―― [カスタム]型でも+、-、<< [などの]演算子を使えるようにします!



