C++ オブジェクト指向プログラミング:カプセル化・深いコピー・動的メモリ管理の実践

課題1:コンポジションによるGUIコンポーネントの設計

ボタンとウィンドウの関係をコンポジション(has-a関係)で表現します。WindowクラスがWidgetクラスを含む形で実装します。

Widget.hpp

#pragma once
#include <iostream>
#include <string>

class Widget {
public:
    explicit Widget(const std::string &name);
    const std::string& name() const;
    void activate();

private:
    std::string widget_name;
};

Widget::Widget(const std::string &name) : widget_name{name} {}

inline const std::string& Widget::name() const {
    return widget_name;
}

inline void Widget::activate() {
    std::cout << "Widget [" << widget_name << "] activated\n";
}

Frame.hpp

#pragma once
#include <iostream>
#include <vector>
#include <algorithm>
#include "widget.hpp"

class Frame {
public:
    explicit Frame(const std::string &caption);
    void render() const;
    void shutdown();
    void attach(const std::string &widget_name);
    void trigger(const std::string &widget_name);

private:
    bool exists(const std::string &name) const;

    std::string caption;
    std::vector<Widget> widgets;
};

Frame::Frame(const std::string &caption_) : caption{caption_} {
    widgets.emplace_back("exit");
}

inline void Frame::render() const {
    std::string border(50, '=');
    std::cout << border << '\n';
    std::cout << "Frame: " << caption << '\n';
    int num = 0;
    for (const auto &w : widgets) {
        std::cout << ++num << ". " << w.name() << '\n';
    }
    std::cout << border << '\n';
}

inline void Frame::shutdown() {
    std::cout << "Shutting down frame '" << caption << "'\n";
    trigger("exit");
}

inline bool Frame::exists(const std::string &name) const {
    return std::any_of(widgets.begin(), widgets.end(),
        [&name](const Widget &w) { return w.name() == name; });
}

inline void Frame::attach(const std::string &widget_name) {
    if (exists(widget_name)) {
        std::cout << "Widget '" << widget_name << "' already exists!\n";
    } else {
        widgets.emplace_back(widget_name);
    }
}

inline void Frame::trigger(const std::string &widget_name) {
    for (auto &w : widgets) {
        if (w.name() == widget_name) {
            w.activate();
            return;
        }
    }
    std::cout << "Widget not found: " << widget_name << '\n';
}

main.cpp

#include "frame.hpp"

void demo() {
    Frame frm("Application");
    frm.attach("create");
    frm.attach("delete");
    frm.attach("update");
    frm.attach("create");  // 重複追加テスト
    frm.render();
    frm.shutdown();
}

int main() {
    std::cout << "=== Composition Pattern Demo ===\n";
    demo();
    return 0;
}

課題2:標準コンテナの深いコピー動作の検証

std::vectorのコピー構築と要素の独立性を確認します。ネストしたベクタの動作も検証します。

#include <iostream>
#include <vector>

void verify_flat_vector();
void verify_nested_vector();
void show_elements(const std::vector<int> &vec);
void show_matrix(const std::vector<std::vector<int>> &mat);

int main() {
    std::cout << "=== 深いコピー検証:1次元vector ===\n";
    verify_flat_vector();
    
    std::cout << "\n=== 深いコピー検証:2次元vector ===\n";
    verify_nested_vector();
}

void verify_flat_vector() {
    std::vector<int> original(5, 100);
    const std::vector<int> duplicate(original);
    
    std::cout << "--- コピー直後 ---\n";
    std::cout << "original: "; show_elements(original);
    std::cout << "duplicate: "; show_elements(duplicate);
    
    original.front() = -999;
    
    std::cout << "--- original[0]変更後 ---\n";
    std::cout << "original: "; show_elements(original);
    std::cout << "duplicate: "; show_elements(duplicate);
}

void verify_nested_vector() {
    std::vector<std::vector<int>> original{{10, 20, 30}, {40, 50, 60, 70}};
    const std::vector<std::vector<int>> duplicate(original);
    
    std::cout << "--- コピー直後 ---\n";
    std::cout << "original:\n"; show_matrix(original);
    std::cout << "duplicate:\n"; show_matrix(duplicate);
    
    original.at(0).push_back(-1);
    
    std::cout << "--- original[0]に要素追加後 ---\n";
    std::cout << "original:\n"; show_matrix(original);
    std::cout << "duplicate:\n"; show_matrix(duplicate);
}

void show_elements(const std::vector<int> &vec) {
    if (vec.empty()) {
        std::cout << "(empty)\n";
        return;
    }
    auto it = vec.begin();
    std::cout << *it;
    for (++it; it != vec.end(); ++it) {
        std::cout << ", " << *it;
    }
    std::cout << '\n';
}

void show_matrix(const std::vector<std::vector<int>> &mat) {
    for (const auto &row : mat) {
        show_elements(row);
    }
}

課題3:カスタム動的配列クラスの実装

RAII原則に基づき、動的メモリ管理を完全にカプセル化したIntArrayクラスを作成します。

#pragma once
#include <iostream>
#include <cstdlib>

class IntArray {
public:
    IntArray();
    explicit IntArray(int length);
    IntArray(int length, int init_val);
    IntArray(const IntArray &other);
    ~IntArray();
    
    int length() const;
    int& access(int idx);
    const int& access(int idx) const;
    IntArray& copy_from(const IntArray &source);
    
    int* data();
    int* data_end();
    const int* data() const;
    const int* data_end() const;

private:
    int size;
    int *buffer;
};

IntArray::IntArray() : size{0}, buffer{nullptr} {}

IntArray::IntArray(int length) : size{length}, buffer{new int[length]} {}

IntArray::IntArray(int length, int init_val) : size{length}, buffer{new int[length]} {
    for (int i = 0; i < size; ++i) {
        buffer[i] = init_val;
    }
}

IntArray::IntArray(const IntArray &other) : size{other.size}, buffer{new int[other.size]} {
    for (int i = 0; i < size; ++i) {
        buffer[i] = other.buffer[i];
    }
}

IntArray::~IntArray() {
    delete[] buffer;
}

int IntArray::length() const {
    return size;
}

const int& IntArray::access(int idx) const {
    if (idx < 0 || idx >= size) {
        std::cerr << "IndexError: position " << idx << " out of bounds\n";
        std::exit(EXIT_FAILURE);
    }
    return buffer[idx];
}

int& IntArray::access(int idx) {
    if (idx < 0 || idx >= size) {
        std::cerr << "IndexError: position " << idx << " out of bounds\n";
        std::exit(EXIT_FAILURE);
    }
    return buffer[idx];
}

IntArray& IntArray::copy_from(const IntArray &source) {
    if (this == &source) return *this;
    
    int *temp = new int[source.size];
    for (int i = 0; i < source.size; ++i) {
        temp[i] = source.buffer[i];
    }
    
    delete[] buffer;
    size = source.size;
    buffer = temp;
    return *this;
}

int* IntArray::data() { return buffer; }
int* IntArray::data_end() { return buffer + size; }
const int* IntArray::data() const { return buffer; }
const int* IntArray::data_end() const { return buffer + size; }

課題4:動的2次元配列(行列)クラスの実装

デリゲートコンストラクタとconst_castによるコード重複排除を活用した行列クラスです。

Matrix.hpp

#pragma once
#include <iostream>
#include <cstdlib>

class Matrix {
public:
    Matrix(int rows, int cols, double init = 0.0);
    explicit Matrix(int dim, double init = 0.0);
    Matrix(const Matrix &other);
    ~Matrix();
    
    void load(const double *src, int count);
    void zero_fill();
    
    double& elem(int row, int col);
    const double& elem(int row, int col) const;
    
    int row_count() const;
    int col_count() const;
    void display() const;

private:
    int rows;
    int cols;
    double *storage;
};

Matrix.cpp

#include "matrix.hpp"

Matrix::Matrix(int row_cnt, int col_cnt, double init)
    : rows{row_cnt}, cols{col_cnt}, storage{new double[row_cnt * col_cnt]} {
    if (row_cnt <= 0 || col_cnt <= 0) {
        std::cerr << "Error: Invalid dimensions\n";
        std::exit(EXIT_FAILURE);
    }
    for (int i = 0; i < rows * cols; ++i) {
        storage[i] = init;
    }
}

Matrix::Matrix(int dim, double init) : Matrix(dim, dim, init) {}

Matrix::Matrix(const Matrix &other)
    : rows{other.rows}, cols{other.cols}, storage{new double[other.rows * other.cols]} {
    for (int i = 0; i < rows * cols; ++i) {
        storage[i] = other.storage[i];
    }
}

Matrix::~Matrix() {
    delete[] storage;
}

void Matrix::load(const double *src, int count) {
    if (src == nullptr) {
        std::cerr << "Error: Null source pointer\n";
        std::exit(EXIT_FAILURE);
    }
    if (count != rows * cols) {
        std::cerr << "Error: Size mismatch\n";
        std::exit(EXIT_FAILURE);
    }
    for (int i = 0; i < count; ++i) {
        storage[i] = src[i];
    }
}

void Matrix::zero_fill() {
    for (int i = 0; i < rows * cols; ++i) {
        storage[i] = 0.0;
    }
}

const double& Matrix::elem(int row, int col) const {
    if (row < 0 || row >= rows || col < 0 || col >= cols) {
        std::cerr << "IndexError: (" << row << "," << col << ") out of range\n";
        std::exit(EXIT_FAILURE);
    }
    return storage[row * cols + col];
}

double& Matrix::elem(int row, int col) {
    return const_cast<double&>(
        static_cast<const Matrix*>(this)->elem(row, col)
    );
}

int Matrix::row_count() const { return rows; }
int Matrix::col_count() const { return cols; }

void Matrix::display() const {
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            if (j > 0) std::cout << ", ";
            std::cout << storage[i * cols + j];
        }
        std::cout << '\n';
    }
}

課題5:連絡先管理システム

ソートと検索機能を持つ連絡先管理クラスを実装します。

Person.hpp

#pragma once
#include <iostream>
#include <string>

class Person {
public:
    Person(const std::string &nm, const std::string &tel);
    
    const std::string& name() const;
    const std::string& phone() const;
    void print() const;

private:
    std::string person_name;
    std::string person_phone;
};

Person::Person(const std::string &nm, const std::string &tel)
    : person_name{nm}, person_phone{tel} {}

const std::string& Person::name() const { return person_name; }
const std::string& Person::phone() const { return person_phone; }

void Person::print() const {
    std::cout << person_name << " | " << person_phone;
}

AddressBook.hpp

#pragma once
#include <vector>
#include <algorithm>
#include "person.hpp"

class AddressBook {
public:
    void register_contact(const std::string &name, const std::string &phone);
    void unregister(const std::string &name);
    void search(const std::string &name) const;
    void list_all() const;
    size_t count() const;

private:
    int locate(const std::string &name) const;
    void reorder();
    
    std::vector<Person> entries;
};

void AddressBook::register_contact(const std::string &name, const std::string &phone) {
    if (locate(name) < 0) {
        entries.emplace_back(name, phone);
        std::cout << "Added: " << name << '\n';
        reorder();
    } else {
        std::cout << "Exists: " << name << " (add failed)\n";
    }
}

void AddressBook::unregister(const std::string &name) {
    int pos = locate(name);
    if (pos >= 0) {
        entries.erase(entries.begin() + pos);
        std::cout << "Removed: " << name << '\n';
    } else {
        std::cout << "Not found: " << name << '\n';
    }
}

void AddressBook::search(const std::string &name) const {
    int pos = locate(name);
    if (pos >= 0) {
        entries[pos].print();
        std::cout << '\n';
    } else {
        std::cout << "No match for: " << name << '\n';
    }
}

void AddressBook::list_all() const {
    for (const auto &e : entries) {
        e.print();
        std::cout << '\n';
    }
}

size_t AddressBook::count() const { return entries.size(); }

int AddressBook::locate(const std::string &name) const {
    for (size_t i = 0; i < entries.size(); ++i) {
        if (entries[i].name() == name) {
            return static_cast<int>(i);
        }
    }
    return -1;
}

void AddressBook::reorder() {
    std::sort(entries.begin(), entries.end(),
        [](const Person &a, const Person &b) {
            return a.name() < b.name();
        });
}

main.cpp

#include "addressbook.hpp"

void run_test() {
    AddressBook book;
    
    std::cout << "=== Registration ===\n";
    book.register_contact("Yamada", "090-1234-5678");
    book.register_contact("Suzuki", "080-8765-4321");
    book.register_contact("Tanaka", "070-1111-2222");
    book.register_contact("Yamada", "090-9999-8888");  // 重複テスト
    
    std::cout << "\n=== Directory (" << book.count() << " entries) ===\n";
    book.list_all();
    
    std::cout << "\n=== Search ===\n";
    book.search("Suzuki");
    book.search("NonExistent");
    
    std::cout << "\n=== Removal ===\n";
    book.unregister("Yamada");
    book.unregister("Unknown");
}

int main() {
    run_test();
    return 0;
}

タグ: C++ OOP RAII 深いコピー コンポジション

8月8日 14:29 投稿