C++: コンストラクタ応用
最終更新:2026-08-31
レッスン28ではコンストラクタの基本を学びました。
でも、デフォルトコンストラクタ、コピーコンストラクタ、初期化リスト……
このレッスンでは、コンストラクタの応用テクニックを学びます。
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; // ✅ デフォルトコンストラクタ呼び出し
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); // ✅ OK
Student s2; // ❌ エラー:デフォルトコンストラクタがない
return 0;
}
解決策: デフォルトコンストラクタを書くか、
= default(C++11)を使います。
// 方法1:手書き
Student() {
name = "Unknown";
age = 0;
}
// 方法2:= default(C++11、推奨)
Student() = default;
2. 初期化リスト(Initializer List)
(1) 2.1 初期化リストとは?
初期化リストはコンストラクタの定義でメンバーを初期化、関数本体で代入しません。
関数本体で代入(非推奨):
Student(const std::string& n, int a) {
name = n; // 代入、初期化ではない(メモリ割り当て後、代入)
age = a;
}
初期化リストで書く(推奨):
Student(const std::string& n, int a) : name(n), age(a) {
// ...
}
- 効率的(初期化、代入ではない)
- 初期化リスト必須のケース:
constメンバー(代入不可)- 参照メンバー(初期化必須)
- デフォルトコンストラクタなしのメンバークラス
(2) 2.2 初期化リストが必須のケース
例:const メンバー
#include <iostream>
#include <string>
class Student {
private:
const std::string id; // constメンバー
std::string name;
public:
// ❌ エラー:constは代入不可
// Student(const std::string& i, const std::string& n) {
// id = i; // ❌ constは代入不可
// name = n;
// }
// ✅ 解決:初期化リストを使用
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 コピーコンストラクタとは?
コピーコンストラクタは既存のオブジェクトを使って新しいオブジェクトを作成するコンストラクタです。
使い道:
- 既存のオブジェクトから新しいオブジェクトを作成
- 関数にオブジェクトを値渡し(pass-by-value)
- 関数がオブジェクトを返す(return)
▶ サンプル 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; // ✅ コピーコンストラクタ呼び出し
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;
}
出力:
TEXT 📖 参照専用コンストラクタ:Alice コピーコンストラクタ:Alice
4. シャローコピー vs ディープコピー
(1) 4.1 問題:シャローコピーの「危険」
クラスにポインタメンバー(ヒープメモリ)がある場合、シャローコピーはポインタの値(アドレス)だけをコピー、ポインタが指すデータはコピーしません。
#include <iostream>
#include <cstring>
class MyClass {
public:
char* data;
MyClass() {
data = new char[5];
std::strcpy(data, "Hello");
}
// ❌ コピーコンストラクタを定義しない(シャローコピー)
~MyClass() {
delete data;
}
};
int main() {
MyClass obj1;
MyClass obj2 = obj1; // シャローコピー:obj1.dataとobj2.dataは同じアドレス
// main終了時、obj2が先に破棄(dataを解放)
// obj1が次に破棄(dataは既に解放済み)→ 二重解放エラー!
return 0;
}
(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は新しいメモリを指す
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; // 元のオブジェクトを空にする(安全)
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 << "私は" << name << "、" << age << "歳です。" << std::endl;
}
};
int main() {
Student s1("Alice", 20);
Student s2 = s1; // コピーコンストラクタ呼び出し
s1.introduce();
s2.introduce();
return 0;
}
▶ サンプル 3:委譲コンストラクタ(難易度 ⭐⭐)
#include <iostream>
#include <string>
class Rectangle {
private:
double width;
double height;
public:
// すべてのパラメータを持つメインコンストラクタ
Rectangle(double w, double h) : width(w), height(h) {
std::cout << "長方形作成: " << width << " x " << height << std::endl;
}
// 委譲コンストラクタ - 正方形を作成
Rectangle(double side) : Rectangle(side, side) {
std::cout << "(正方形)" << std::endl;
}
// デフォルトコンストラクタ - 正方形に委譲
Rectangle() : Rectangle(1.0) {
std::cout << "(デフォルトの単位正方形)" << std::endl;
}
double area() const { return width * height; }
};
int main() {
Rectangle r1(3.0, 4.0);
Rectangle r2(5.0); // 正方形
Rectangle r3; // デフォルト
return 0;
}
出力:
TEXT 📖 参照専用長方形作成: 3 x 4 長方形作成: 5 x 5 (正方形) 長方形作成: 1 x 1 (正方形) (デフォルトの単位正方形)
❓ よくある質問
クラスが基本型(int、double)や STL 型
std::string、std::vector)では初期化リストが効率的 ——
- 関数本体で代入:デフォルトコンストラクタ呼び出し → 代入(メモリ割り当てが2回)
- 初期化リスト:直接初期化(メモリ割り当て1回)
推奨:
関数がオブジェクトを返すとき、ムーブコンストラクタで無駄なコピーを避ける
📖 まとめ
- デフォルトコンストラクタは引数なしのコンストラクタ
- 初期化リストは効率的、
constメンバー、参照メンバーに必須 - コピーコンストラクタは既存のオブジェクトから新しいオブジェクトを作成
- クラスにポインタメンバーがある場合、ディープコピーコンストラクタを定義
- ムーブコンストラクタ(C++11)は一時オブジェクトのリソースを盗む
📝 練習問題
-
初級(難易度 ⭐):
Bookクラスを定義し、タイトル、著者、価格のメンバーを持たせてください。初期化リストでコンストラクタを書く。 -
中級(難易度 ⭐⭐):
Bookクラスのコピーコンストラクタを定義し、ディープコピーを実装(char*を使い、メモリを確保)。 -
上級(難易度 ⭐⭐⭐):
DynamicArrayクラスを定義(動的配列)、以下を持つ:int* data(ヒープメモリ)int size(配列サイズ)
実装:
- コンストラクタ(メモリ確保)
- コピーコンストラクタ(ディープコピー)
- デストラクタ(メモリ解放)
テスト:
DynamicArray obj1(10)、obj1からobj2を作成、両方のオブジェクトのdataのメモリアドレスを出力。
7. 🚀 次のステップ
コンストラクタの応用を理解したら、次は 演算子オーバーロード(レッスン30) —— 自分の型で +、-、<< などの演算子を使えるようにしよう!