クラステンプレート 解答ページ | Programming Place Plus 新C++編

トップページ新C++編クラステンプレート

このページの概要 🔗

このページは、練習問題の解答例や解説のページです。



解答・解説 🔗

問題1 (確認★) 🔗

次の用語について説明してください。


「クラステンプレート」(本編解説)は、クラスの実装の一部を空欄にした状態で作成しておき、使用者の側で空欄部分を埋めて使えるようにしたものです。空欄は、クラステンプレートの実装内ではテンプレート仮引数で表現し、使用者が指定したテンプレート実引数によって置き換えられます。特定の型に限定されないプログラムを実現できる重要な機能です。

「テンプレートのインスタンス化」(本編解説)は、テンプレート実引数を指定し、クラステンプレートの実装内のテンプレート仮引数が使われている部分を置き換えることによって、前述した「空欄」がない使用できる状態のクラスを生成することです。

「テンプレートの特殊化」(本編解説)は、テンプレートのインスタンス化によって生成された実体(ここではクラス)のことです。

「依存名」(本編解説)は、テンプレート仮引数と関係性がある名前のことです。それが型の名前のようにみえても、コンパイラのルール上は、型ではないものとして扱われてしまうことがあります。それが型であることを typenameキーワードを補って明示的に示す必要があります。

「テンプレートテンプレート仮引数」(本編解説)は、テンプレート仮引数にクラステンプレートを使えるようにする機能です。通常のテンプレート仮引数の構文では、クラステンプレートを指定することはできないので、テンプレートテンプレート仮引数を使わなければなりません。

問題2 (基本★) 🔗

2次元の座標を表現する Point クラステンプレートを作成してください。座標を任意の型で表現できるようにしてください。


たとえば次のように作成できます。

// point.h
#ifndef POINT_H_INCLUDED
#define POINT_H_INCLUDED

namespace mylib {

    // 2次元座標
    template <typename T>
    class Point {
    public:
        Point(T x, T y) :
            m_x {x}, m_y {y}
        {}

        inline void set_x(T x)
        {
            m_x = x;
        }

        inline void set_y(T y)
        {
            m_y = y;
        }

        inline T get_x() const
        {
            return m_x;
        }

        inline T get_y() const
        {
            return m_y;
        }

    private:
        T       m_x;
        T       m_y;
    };

}

#endif
// main.cpp
#include <iostream>
#include "point.h"

int main()
{
    mylib::Point<int> p1(10, 20);
    mylib::Point<double> p2(5.5, 7.5);

    p1.set_x(40);
    std::cout << p1.get_x() << ", " << p1.get_y() << "\n";

    p2.set_y(-1.5);
    std::cout << p2.get_x() << ", " << p2.get_y() << "\n";
}

実行結果:

40, 20
5.5, -1.5

コンストラクタや、set系のメンバ関数の仮引数を T とすべきか const T& とすべきかは迷うところかもしれません。T がサイズの大きい型であれば参照を使ったほうが効率は良いですが、テンプレート実引数に何が指定されるか次第ということになるので、一概にはいえません。効率や使いやすさなどを踏まえてうまくテンプレートを設計することは非常に難易度が高いことです。

この程度の規模ならばいきなりクラステンプレートを作り始めてもいいかもしれませんが、不安な場合はいったん int型に固定したクラスを書いてみるのも手です。

// point.h
#ifndef POINT_H_INCLUDED
#define POINT_H_INCLUDED

namespace mylib {

    // 2次元座標
    class Point {
    public:
        Point(int x, int y) :
            m_x {x}, m_y {y}
        {}

        inline void set_x(int x)
        {
            m_x = x;
        }

        inline void set_y(int y)
        {
            m_y = y;
        }

        inline int get_x() const
        {
            return m_x;
        }

        inline int get_y() const
        {
            return m_y;
        }

    private:
        int     m_x;
        int     m_y;
    };

}

#endif
// main.cpp
#include <iostream>
#include "point.h"

int main()
{
    mylib::Point p1(10, 20);

    p1.set_x(40);
    std::cout << p1.get_x() << ", " << p1.get_y() << "\n";
}

実行結果:

40, 20

確実に動くクラスを作れたら、template <typename T> を加え、いったん int にしていた箇所を T に置き換えれば、目的のクラステンプレートが作れます。

問題3 (応用★★★) 🔗

std::stack(「再帰呼び出しとスタック」のページを参照)の作りを参考にして、スタックのクラステンプレートを作成してください。


たとえば次のように作成できます。

// stack.h
#ifndef STACK_H_INCLUDED
#define STACK_H_INCLUDED

#include <deque>

namespace mylib {

    // スタック
    template <typename T>
    class Stack {
    public:
        using container_type = typename std::deque<T>;                      // 内部コンテナの型
        using value_type = typename container_type::value_type;             // 要素型
        using reference = typename container_type::reference;               // 要素の参照型
        using const_reference = typename container_type::const_reference;   // 要素の const参照型
        using size_type = typename container_type::size_type;               // サイズ型

    public:
        // コンストラクタ
        Stack() = default;

        // コンストラクタ
        Stack(const Stack& other) : m_data {other.m_data}
        {}

        // プッシュ
        inline void push(const_reference value)
        {
            m_data.push_back(value);
        }

        // ポップ
        inline void pop()
        {
            m_data.pop_back();
        }

        // 次にポップされる要素の参照を返す
        inline reference top()
        {
            return m_data.back();
        }

        // 次にポップされる要素の参照を返す
        inline const_reference top() const
        {
            return m_data.back();
        }

        // 空かどうか調べる
        inline bool empty() const
        {
            return m_data.empty();
        }

        // 要素数を返す
        inline size_type size() const
        {
            return m_data.size();
        }

    private:
        container_type  m_data;
    };

}

#endif
// main.cpp
#include <iostream>
#include "stack.h"

int main()
{
    mylib::Stack<int> istack {};
    for (int i = 0; i < 5; ++i) {
        istack.push(i);
    }
    while (!istack.empty()) {
        auto value = istack.top();
        istack.pop();
        std::cout << value << "\n";
    }

    mylib::Stack<char> cstack {};
    for (int i = 0; i < 5; ++i) {
        cstack.push('a' + i);
    }
    while (!cstack.empty()) {
        auto value = cstack.top();
        cstack.pop();
        std::cout << value << "\n";
    }
}

実行結果:

4
3
2
1
0
e
d
c
b
a

std::stack の内部コンテナのデフォルトは std::deque なので、それに合わせて実装しています。typedef名をエイリアス宣言で定義するとき、元の型名が依存名なので、typenameキーワードを挟む必要があります(本編解説)。

こうやって作ってみると、内部コンテナをどのように使っているのかよく分かると思います。そして、内部コンテナを切り替えられる理屈が分かり、なぜ特定のメンバ関数の存在が要求されているのか(「queue、priority_queue、deque」のページを参照)もはっきりします。


参考リンク 🔗



更新履歴 🔗




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る