第3章:テストとデバッグ

はじめに

本章では、CPP Module 04のテストとデバッグ方法を解説します。

---

1. 仮想関数のテスト

1.1 ポリモーフィズムの確認

void testPolymorphism() {
    std::cout << "=== Polymorphism Test ===" << std::endl;

    const Animal* animal = new Animal();
    const Animal* dog = new Dog();
    const Animal* cat = new Cat();

    std::cout << "Animal type: " << animal->getType() << std::endl;
    std::cout << "Dog type: " << dog->getType() << std::endl;
    std::cout << "Cat type: " << cat->getType() << std::endl;

    std::cout << "\nMaking sounds:" << std::endl;
    animal->makeSound();  // "..."
    dog->makeSound();     // "Woof!"
    cat->makeSound();     // "Meow!"

    delete animal;
    delete dog;
    delete cat;
}

1.2 WrongAnimalとの比較

void testWrongPolymorphism() {
    std::cout << "=== Wrong Polymorphism Test ===" << std::endl;

    const WrongAnimal* wrongAnimal = new WrongAnimal();
    const WrongAnimal* wrongCat = new WrongCat();

    std::cout << "WrongCat type: " << wrongCat->getType() << std::endl;

    std::cout << "\nMaking sounds:" << std::endl;
    wrongAnimal->makeSound();  // WrongAnimalの音
    wrongCat->makeSound();     // WrongAnimalの音!(virtualなし)

    delete wrongAnimal;
    delete wrongCat;
}

---

2. 仮想デストラクタのテスト

2.1 正しいデストラクタ順序

void testVirtualDestructor() {
    std::cout << "=== Virtual Destructor Test ===" << std::endl;

    Animal* dog = new Dog();
    std::cout << "\nDeleting through base pointer:" << std::endl;
    delete dog;
    // 期待: Dog destructor → Animal destructor
}

2.2 Valgrindでメモリリーク確認

valgrind --leak-check=full ./animals

# 期待される結果(ex01以降):
# All heap blocks were freed -- no leaks are possible

---

3. 深いコピーのテスト

3.1 コピーの独立性確認

void testDeepCopy() {
    std::cout << "=== Deep Copy Test ===" << std::endl;

    Dog original;
    original.getBrain()->ideas[0] = "Original idea";
    original.getBrain()->ideas[1] = "Another idea";

    std::cout << "\n--- Copy Constructor ---" << std::endl;
    Dog copy1(original);

    std::cout << "\n--- Assignment Operator ---" << std::endl;
    Dog copy2;
    copy2 = original;

    // アドレスが異なることを確認
    std::cout << "\nBrain addresses:" << std::endl;
    std::cout << "Original: " << original.getBrain() << std::endl;
    std::cout << "Copy1:    " << copy1.getBrain() << std::endl;
    std::cout << "Copy2:    " << copy2.getBrain() << std::endl;

    // 内容の確認
    std::cout << "\nIdeas:" << std::endl;
    std::cout << "Original[0]: " << original.getBrain()->ideas[0] << std::endl;
    std::cout << "Copy1[0]:    " << copy1.getBrain()->ideas[0] << std::endl;
    std::cout << "Copy2[0]:    " << copy2.getBrain()->ideas[0] << std::endl;

    // 独立性の確認
    copy1.getBrain()->ideas[0] = "Modified in copy1";
    std::cout << "\nAfter modifying copy1:" << std::endl;
    std::cout << "Original[0]: " << original.getBrain()->ideas[0] << std::endl;
    std::cout << "Copy1[0]:    " << copy1.getBrain()->ideas[0] << std::endl;
}

3.2 配列のテスト

void testAnimalArray() {
    std::cout << "=== Animal Array Test ===" << std::endl;

    const int count = 10;
    Animal* animals[count];

    std::cout << "\n--- Creating animals ---" << std::endl;
    for (int i = 0; i < count / 2; i++) {
        animals[i] = new Dog();
    }
    for (int i = count / 2; i < count; i++) {
        animals[i] = new Cat();
    }

    std::cout << "\n--- Making sounds ---" << std::endl;
    for (int i = 0; i < count; i++) {
        std::cout << animals[i]->getType() << ": ";
        animals[i]->makeSound();
    }

    std::cout << "\n--- Deleting animals ---" << std::endl;
    for (int i = 0; i < count; i++) {
        delete animals[i];
    }
}

---

4. 抽象クラスのテスト

4.1 コンパイル時チェック

void testAbstractClass() {
    // 以下はコンパイルエラーになるべき
    // Animal animal;  // Error: cannot instantiate abstract class

    // ポインタ/参照は可能
    Animal* ptr = new Dog();
    Animal& ref = *ptr;

    ptr->makeSound();
    ref.makeSound();

    delete ptr;
}

---

5. インターフェースのテスト(ex03)

5.1 基本機能テスト

void testMateriaBasic() {
    std::cout << "=== Materia Basic Test ===" << std::endl;

    IMateriaSource* src = new MateriaSource();
    src->learnMateria(new Ice());
    src->learnMateria(new Cure());

    ICharacter* me = new Character("Hero");

    AMateria* ice = src->createMateria("ice");
    AMateria* cure = src->createMateria("cure");

    me->equip(ice);
    me->equip(cure);

    ICharacter* target = new Character("Enemy");

    me->use(0, *target);  // ice
    me->use(1, *target);  // cure

    delete target;
    delete me;
    delete src;
}

5.2 unequipテスト

void testUnequip() {
    std::cout << "=== Unequip Test ===" << std::endl;

    Character hero("Hero");
    AMateria* ice = new Ice();

    hero.equip(ice);
    hero.unequip(0);  // iceはdeleteされない

    // メモリリーク防止のため手動で削除
    delete ice;
}

5.3 深いコピーテスト

void testCharacterDeepCopy() {
    std::cout << "=== Character Deep Copy Test ===" << std::endl;

    Character original("Original");
    original.equip(new Ice());
    original.equip(new Cure());

    Character copy = original;

    ICharacter* target = new Character("Target");

    std::cout << "Original using:" << std::endl;
    original.use(0, *target);
    original.use(1, *target);

    std::cout << "Copy using:" << std::endl;
    copy.use(0, *target);
    copy.use(1, *target);

    delete target;
}

---

6. よくある間違い

6.1 仮想デストラクタの欠如

// 間違い
class Animal {
public:
    ~Animal() { }  // 非仮想デストラクタ
};

class Dog : public Animal {
    Brain* brain;
public:
    Dog() : brain(new Brain()) { }
    ~Dog() { delete brain; }  // 呼ばれない可能性
};

// 正しい
class Animal {
public:
    virtual ~Animal() { }  // 仮想デストラクタ
};

6.2 浅いコピー

// 間違い
Dog::Dog(const Dog& other) : Animal(other) {
    brain = other.brain;  // 浅いコピー
}

// 正しい
Dog::Dog(const Dog& other) : Animal(other) {
    brain = new Brain(*other.brain);  // 深いコピー
}

6.3 代入演算子でのリーク

// 間違い
Dog& Dog::operator=(const Dog& other) {
    brain = new Brain(*other.brain);  // 古いbrainがリーク
    return *this;
}

// 正しい
Dog& Dog::operator=(const Dog& other) {
    if (this != &other) {
        delete brain;  // 古いbrainを解放
        brain = new Brain(*other.brain);
    }
    return *this;
}

6.4 unequipでのdelete

// 間違い
void Character::unequip(int idx) {
    delete inventory[idx];  // 外部でまだ使う可能性
    inventory[idx] = NULL;
}

// 正しい
void Character::unequip(int idx) {
    inventory[idx] = NULL;  // deleteしない
}

6.5 clone()の実装忘れ

// 間違い
AMateria* Ice::clone() const {
    return new Ice();  // 現在の状態がコピーされない
}

// 正しい
AMateria* Ice::clone() const {
    return new Ice(*this);  // コピーコンストラクタを使用
}

---

7. 提出前チェックリスト

ex00

  • [ ] Animalクラスが正しく動作
  • [ ] Dog/Catが正しくオーバーライド
  • [ ] 仮想デストラクタがある
  • [ ] WrongAnimal/WrongCatで違いを確認
  • ex01

  • [ ] Brainクラスが正しく動作
  • [ ] Dog/CatにBrain*がある
  • [ ] 深いコピーが正しく動作
  • [ ] Valgrindでメモリリークなし
  • ex02

  • [ ] Animalが抽象クラス
  • [ ] makeSound()が純粋仮想関数
  • [ ] Animalを直接インスタンス化できない
  • ex03

  • [ ] AMateria抽象クラスが正しい
  • [ ] Ice/Cureがclone()を実装
  • [ ] Characterが4つのスロットを持つ
  • [ ] unequipがdeleteしない
  • [ ] MateriaSourceがファクトリとして動作
  • [ ] 深いコピーが正しい
  • [ ] メモリリークなし
  • ---

    まとめ

    CPP Module 04のテストでは以下を確認:

  • 仮想関数: 動的バインディングが正しく動作
  • 仮想デストラクタ: メモリリークなし
  • 深いコピー: 独立したオブジェクト
  • 抽象クラス: インスタンス化不可
  • インターフェース: 正しい実装とメモリ管理