配列 解答ページ | Programming Place Plus Modern C++編【言語解説】 第16章

トップページModern C++編 C++編](../../index.html) – 第16章

Modern C++編は作りかけで、更新が停止しています。代わりに、C++14 をベースにして、その他の方針についても見直しを行った、新C++編を作成しています。
Modern C++編は削除される予定です。

問題①

問題① 次のプログラムは動作するでしょうか?

#include <iostream>

int main()
{
    for (int v : {0, 1, 2, 3, 4}) {
        std::cout << v << std::endl;
    }
}


{0, 1, 2, 3, 4} の部分は、std::initializer_list になります。std::initializer_list は、beginメンバ関数と endメンバ関数を持っており、範囲for文を適用できますから、このプログラムは動作します。

問題②

問題② 次のような、動的配列を扱うクラステンプレートがあるとします。

template <typename T>
class FixedSizeVector {
public:
    explicit FixedSizeVector(std::size_t size);
    ~FixedSizeVector();

private:
    T*  mData;
};

要素数をコンストラクタの実引数で指定するとして、コンストラクタとデストラクタを実装してください。


動的な配列の生成には new[] を、その解放には delete[] を使います。

template <typename T>
class FixedSizeVector {
public:
    explicit FixedSizeVector(std::size_t size) :
        mData(new T[size])
    {
    }

    ~FixedSizeVector()
    {
        delete [] mData;
    }

private:
    T*  mData;
};

問題③

問題③ 問題②のクラステンプレートに、以下の機能を追加してください。


範囲for文を使えるようにするには、beginメンバ関数と endメンバ関数が必要です。オブジェクトが const でも非const でも使えるようにするために、const版と非const版をそれぞれ定義します。これらの関数の戻り値型は、T* や const T* でもいいですが、標準ライブラリに合わせて、iterator とか const_iterator という名前のメンバ型にしておくと、なお良いでしょう。

また、末尾要素の位置が分からないと実装できないので、コンストラクタで要素数を取っておくようにします。

template<typename T>
class FixedSizeVector {
public:
    using iterator = T*;
    using const_iterator = const T*;

public:
    explicit FixedSizeVector(std::size_t size) :
        mSize(size), mData(new T[mSize])
    {
    }

    ~FixedSizeVector()
    {
        delete[] mData;
    }

    iterator begin()
    {
        return mData;
    }
    const_iterator begin() const
    {
        return mData;
    }

    iterator end()
    {
        return mData + mSize;
    }
    const_iterator end() const
    {
        return mData + mSize;
    }

private:
    std::size_t  mSize;
    T*  mData;
};

リスト初期化を受け付けるためには、仮引数が std::initializer_list のコンストラクタを追加します。

FixedSizeVector(std::initializer_list<T> lst) :
    mSize(lst.size()), mData(new T[mSize])
{
    T* p = mData;
    for (const T& e : lst) {
        *p = e;
        ++p;
    }
}

動作を確認してみましょう。

int main()
{
    FixedSizeVector<int> v1 = {0, 1, 2};
    const FixedSizeVector<const char*> v2 = {"a", "abc"};

    for (int n : v1) {
        std::cout << n << std::endl;
    }
    for (const char* s : v2) {
        std::cout << s << std::endl;
    }
}

実行結果:

0
1
2
a
abc

問題④

問題④ 問題③のクラステンプレートのメンバ変数の動的配列を、std::vector に置き換えてください。


FixedSizeVector のイテレータを、T* のままとするか、std::vector の iterator にするかで違いが出てきます。FixedSizeVector を、std::vector の要素数をつねに固定したものという捉え方をするなら、後者の方が自然です。あくまでも std::vector とは異なるものであって、たまたま実装に std::vector を使っているのだと捉えるのなら、T* のままでも良いでしょう。

イテレータの型を T* のまま使うなら、次のように実装できます。

template<typename T>
class FixedSizeVector {
public:
    using iterator = T*;
    using const_iterator = const T*;

public:
    explicit FixedSizeVector(std::size_t size) :
        mData(size)
    {
    }

    FixedSizeVector(std::initializer_list<T> lst) :
        mData(lst)
    {
    }

    iterator begin()
    {
        return mData.data();
    }
    const_iterator begin() const
    {
        return mData.data();
    }

    iterator end()
    {
        return mData.data() + mData.size();
    }
    const_iterator end() const
    {
        return mData.data() + mData.size();
    }

private:
    std::vector<T>  mData;
};

std::vector のコンストラクタは、要素数を指定できるものも、std::initializer_list を指定できるものも、それぞれありますから(【標準ライブラリ】第6章)、単純に渡されてきた引数を横流しする形で十分です。
また、delete [] の役目が std::vector に移されるので、明示的なデストラクタは不要になります。

beginメンバ関数、endメンバ関数の実装では、「mData.begin()」や「mData.end()」を使うことができません。これは、std::vector<T>::iterator と T* は、異なる型だからです。標準ライブラリの実装によっては、結局同じ型になっていることもあり得ますが、保証されたものではありません(【標準ライブラリ】第6章)。
ここでは、dataメンバ関数(【標準ライブラリ】第6章)で先頭要素のメモリアドレスを取得し、sizeメンバ関数で得た要素数分だけ進めた位置を返すようにしています。

一方、イテレータの型を std::vector の iterator にするのなら、次のようになります。

template<typename T>
class FixedSizeVector {
public:
    using iterator = typename std::vector<T>::iterator;
    using const_iterator = typename std::vector<T>::const_iterator;

public:
    explicit FixedSizeVector(std::size_t size) :
        mData(size)
    {
    }

    FixedSizeVector(std::initializer_list<T> lst) :
        mData(lst)
    {
    }

    iterator begin()
    {
        return mData.begin();
    }
    const_iterator begin() const
    {
        return mData.begin();
    }

    iterator end()
    {
        return mData.end();
    }
    const_iterator end() const
    {
        return mData.end();
    }

private:
    std::vector<T>  mData;
};

iterator と const_iterator を定義するとき、typename指定子が必要になることに注意してください。「std::vector<T>::iterator」という部分が、T というテンプレート仮引数を含んでいるため、全体として型名であることを明確にするために、typename指定子が必要です(第8章)。

今度は、beginメンバ関数、endメンバ関数の実装は単純に、mData から得られるイテレータをそのまま返せばよいです。



参考リンク


更新履歴

’2017/12/22 新規作成。



第16章のメインページへ

Modern C++編のトップページへ

Programming Place Plus のトップページへ



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