C++: コンストラクタ応用

最終更新:2026-08-31

レッスン28ではコンストラクタの基本を学びました。

でも、デフォルトコンストラクタコピーコンストラクタ初期化リスト……

このレッスンでは、コンストラクタの応用テクニックを学びます。


1. デフォルトコンストラクタ

(1) 1.1 デフォルトコンストラクタとは?

デフォルトコンストラクタ引数なしのコンストラクタです。

CPP
#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 問題:引数ありコンストラクタを書くとデフォルトコンストラクタがない

CPP
#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)を使います。

TEXT 📖 参照専用
// 方法1:手書き
Student() {
    name = "Unknown";
    age = 0;
}

// 方法2:= default(C++11、推奨)
Student() = default;


2. 初期化リスト(Initializer List)

(1) 2.1 初期化リストとは?

初期化リストはコンストラクタの定義でメンバーを初期化、関数本体で代入しません。

関数本体で代入(非推奨):

CPP
Student(const std::string& n, int a) {
    name = n; // 代入、初期化ではない(メモリ割り当て後、代入)
    age = a;
}

初期化リストで書く(推奨):

TEXT 📖 参照専用
Student(const std::string& n, int a) : name(n), age(a) {
    // ...
}
💡 メリット:

  1. 効率的(初期化、代入ではない)
  2. 初期化リスト必須のケース:
    • const メンバー(代入不可)
    • 参照メンバー(初期化必須)
    • デフォルトコンストラクタなしのメンバークラス

(2) 2.2 初期化リストが必須のケース

例:const メンバー

CPP
#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 コピーコンストラクタとは?

コピーコンストラクタ既存のオブジェクトを使って新しいオブジェクトを作成するコンストラクタです。

使い道:

  1. 既存のオブジェクトから新しいオブジェクトを作成
  2. 関数にオブジェクトを値渡し(pass-by-value)
  3. 関数がオブジェクトを返す(return)

▶ サンプル 1:コンパイラ生成のコピーコンストラクタ(難易度 ⭐)

CPP
#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 コピーコンストラクタを定義

CPP
#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 問題:シャローコピーの「危険」

クラスにポインタメンバー(ヒープメモリ)がある場合、シャローコピーはポインタの値(アドレス)だけをコピー、ポインタが指すデータはコピーしません。

TEXT 📖 参照専用
#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 解決策:ディープコピーを定義

CPP
#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 の新機能 —— 「リソースを盗む」 だけで、コピーしません。

使い道: 一時オブジェクト(右辺値)から新しいオブジェクトを作る —— 「リソースを盗む」だけ。

CPP
#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 クラス(難易度 ⭐⭐)

CPP
#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:委譲コンストラクタ(難易度 ⭐⭐)

CPP
#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
(正方形)
(デフォルトの単位正方形)

❓ よくある質問

Q コピーコンストラクタを定義すべき?
A クラスにポインタメンバー(ヒープメモリ)がある場合、必須。

クラスが基本型(intdouble)や STL 型

Q 初期化リストと関数本体での代入、どちらを使う?
A 基本型ではどちらでもOK。でも STL 型(std::stringstd::vector)では初期化リストが効率的 ——

  • 関数本体で代入:デフォルトコンストラクタ呼び出し → 代入(メモリ割り当てが2回)
  • 初期化リスト:直接初期化(メモリ割り当て1回)

推奨:

Q ムーブコンストラクタはいつ使う?
A 一時オブジェクトから新しいオブジェクトを作る場合。

関数がオブジェクトを返すとき、ムーブコンストラクタで無駄なコピーを避ける


📖 まとめ

  • デフォルトコンストラクタは引数なしのコンストラクタ
  • 初期化リストは効率的、const メンバー、参照メンバーに必須
  • コピーコンストラクタは既存のオブジェクトから新しいオブジェクトを作成
  • クラスにポインタメンバーがある場合、ディープコピーコンストラクタを定義
  • ムーブコンストラクタ(C++11)は一時オブジェクトのリソースを盗む

📝 練習問題

  1. 初級(難易度 ⭐): Book クラスを定義し、タイトル、著者、価格のメンバーを持たせてください。初期化リストでコンストラクタを書く。

  2. 中級(難易度 ⭐⭐): Book クラスのコピーコンストラクタを定義し、ディープコピーを実装(char* を使い、メモリを確保)。

  3. 上級(難易度 ⭐⭐⭐): DynamicArray クラスを定義(動的配列)、以下を持つ:

    • int* data(ヒープメモリ)
    • int size(配列サイズ)

実装:

  • コンストラクタ(メモリ確保)
  • コピーコンストラクタ(ディープコピー)
  • デストラクタ(メモリ解放)

テスト:DynamicArray obj1(10)obj1 から obj2 を作成、両方のオブジェクトの data のメモリアドレスを出力。



7. 🚀 次のステップ

コンストラクタの応用を理解したら、次は 演算子オーバーロード(レッスン30) —— 自分の型で +-<< などの演算子を使えるようにしよう!

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%