演算子のオーバーロード 解答ページ | Programming Place Plus 新C++編

トップページ新C++編演算子のオーバーロード

このページの概要 🔗

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



解答・解説 🔗

問題1 (確認★) 🔗

std::string のオブジェクトs1、s2 があるとき、s1 + s2s1 + "xyz""abc" + s2 が可能であるのはどのような仕組みによるものと考えられますか? また、"abc" + "xyz" が行えないのはなぜですか?


+ という演算子は本来、2つの算術型を加算するか、配列を指すポインタが指し示す要素を移動する用途で使うものであって、文字列を連結するという機能は持っていません。これを可能にしている仕組みはいうまでもなく、演算子のオーバーロードです(本編解説)。

s1 + s2s1 + "xyz""abc" + s2 が可能なのは、標準ライブラリで + がオーバーロードされているからです。std::string は本来 std::basic_string<char> の別名なので(「クラステンプレート」のページを参照)、本当はもっと複雑な宣言になっていますが、std::string に直して簡潔に書くと、以下のような演算子関数が宣言されています。

namespace std {
    string operator+(const string& lhs, const string& rhs);
    string operator+(const char* lhs, const string& rhs);
    string operator+(const string& lhs, const char* rhs);
}

より正しい宣言はリファレンスサイトなどで確認してください1

一方、"abc" + "xyz" が行えないのは、2つのオペランドがいずれも const char[] という型であるため、演算子のオーバーロードを用意できないためです。演算子関数は、非静的なメンバ関数にするか、非メンバ関数にしなければならず、後者の場合は仮引数に、クラス型、クラスの参照型、列挙型、列挙型の参照型のいずれかを含んでいなければなりません(本編解説)。

問題2 (基本★★) 🔗

&&||, をオーバーロードすると、オペランドが評価されるルールに変化が起こることについて説明してください。


まずこれらの演算子の本来の挙動をおさらいしておきましょう。

&& は左オペランドを先に評価し、この結果が false であれば右オペランドの評価を省略します。この動作を短絡評価と呼びます。|| にも短絡評価のルールがあり、左オペランドの評価結果が true であれば、右オペランドの評価が省略されます(「要素を探索する」のページを参照)。

, は左オペランドを先に評価し、そこで作られた結果の値は捨てられます。そのあと右オペランドを評価し、その結果が全体としての評価結果となります(「if文と条件演算子」のページを参照)。

さて、これらの演算子をオーバーロードすると、演算子を使っている式は、演算子関数を呼び出すという動作に変更されます。非静的なメンバ関数として宣言した場合、a && b なら a.operator(b) という関数呼び出しになるということです。a がどうであれ、実引数を作り出すために b を評価しなければならないことが分かります。こうして短絡評価のルールは破壊されます。

また、ab がどちらから評価されるかについても保証がありません。メンバ関数の本体で thisポインタが指し示すものは a ですから(「クラス」のページを参照)、メンバ関数を呼び出す際に a を指すポインタが暗黙的に渡されています。つまり、結局これも引数であり、実引数の評価順は保証されていません(「関数を作る」のページを参照)。

, をオーバーロードしたときの問題もこれと同じです。a, b は本来、a が評価されてから、b が評価されるルールですが、a.operator,(b) という呼び出しに展開されると、ab が評価される順序に保証がなくなります。

問題3 (基本★★) 🔗

RingBufferクラステンプレートに以下の演算子をオーバーロードしてください。

現状の RingBufferクラステンプレートの実装は、「オーバーロード」のページにあります。


==!= は、真逆の結果になることが保証できなければならないので、セットでオーバーロードするべきです。== に具体的な実装を書き、!=operator== の結果を反転して返すようにします(本編解説)。

コピー代入演算子は、operator = を定義することでオーバーロードできます(本編解説)。

    template <typename T>
    RingBuffer& RingBuffer<T>::operator=(const RingBuffer& rhs)
    {
        m_data = rhs.m_data;
        m_size = rhs.m_size;
        m_back = rhs.m_back;
        m_front = rhs.m_front;
        return *this;
    }

ただし、コピー代入演算子は、明示的に定義しなければ暗黙的に作られます(本編解説)。このサンプルコードのように、単純にすべての非静的なデータメンバをコピーするだけの実装になるのなら、=default を使って済ませるほうが良いでしょう。

次のように実装できます。

// ring_buffer.h
#ifndef RING_BUFFER_H_INCLUDED
#define RING_BUFFER_H_INCLUDED

#include <algorithm>
#include <cassert>
#include <vector>

namespace mylib {

    // リングバッファ
    template <typename T>
    class RingBuffer {

        template <typename T>
        friend class RingBuffer;

    public:
        using container_type = typename std::vector<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 pointer = typename container_type::pointer;                   // 要素のポインタ型
        using const_pointer = typename container_type::const_pointer;       // 要素の constポインタ型
        using size_type = typename container_type::size_type;               // サイズ型

    public:
        // コンストラクタ
        //
        // size: 容量
        explicit RingBuffer(size_type capacity);

        // テンプレート変換コンストラクタ
        template <typename U>
        RingBuffer(const RingBuffer<U>& other);


        // コピー代入演算子
        RingBuffer& operator=(const RingBuffer& rhs) = default;
        
        // ==演算子
        bool operator==(const RingBuffer& rhs) const;

        // !=演算子
        inline bool operator!=(const RingBuffer& rhs) const
        {
            return !(*this == rhs);
        }


        // 要素を追加
        void push_back(const value_type& value);

        // 要素を取り除く
        void pop_front();

        // 空にする
        void clear();


        // 先頭の要素の参照を返す
        inline reference front()
        {
            return common_front(this);
        }

        // 先頭の要素の参照を返す
        inline const_reference front() const
        {
            return common_front(this);
        }

        // 末尾の要素の参照を返す
        inline reference back()
        {
            return common_back(this);
        }

        // 末尾の要素の参照を返す
        inline const_reference back() const
        {
            return common_back(this);
        }



        // 容量を返す
        inline size_type capacity() const
        {
            return m_data.size();
        }

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

        // 空かどうかを返す
        inline bool empty() const
        {
            return m_size == 0;
        }

        // 満杯かどうかを返す
        inline bool full() const
        {
            return m_size == capacity();
        }

    private:
        // 次の位置を返す
        inline size_type get_next_pos(size_type pos) const
        {
            return (pos + 1) % capacity();
        }

        // 手前の位置を返す
        inline size_type get_prev_pos(size_type pos) const
        {
            if (pos >= 1) {
                return pos - 1;
            }
            else {
                return m_size - 1;
            }
        }

        template <typename T>
        inline static auto& common_front(T* self)
        {
            assert(self->empty() == false);
            return self->m_data[self->m_front];
        }

        template <typename T>
        inline static auto& common_back(T* self)
        {
            assert(self->empty() == false);
            return self->m_data[self->get_prev_pos(self->m_back)];
        }

    private:
        container_type              m_data;         // 要素
        size_type                   m_size{0};      // 有効な要素の個数
        size_type                   m_back{0};      // 次に push される位置
        size_type                   m_front{0};     // 次に pop される位置
    };


    // コンストラクタ
    template <typename T>
    RingBuffer<T>::RingBuffer(size_type capacity) : m_data(capacity)
    {
    }

    // コンストラクタ(異なる要素型の RingBuffer から作成)
    template <typename T>
    template <typename U>
    RingBuffer<T>::RingBuffer(const RingBuffer<U>& other) :
        m_data(other.m_data.capacity()),
        m_size {other.m_size},
        m_back {other.m_back},
        m_front {other.m_front}
    {
        std::transform(
            std::cbegin(other.m_data),
            std::cend(other.m_data),
            std::begin(m_data),
            [](const auto& e) {
                return static_cast<T>(e);
            }
        );
    }

    // ==演算子
    template <typename T>
    bool RingBuffer<T>::operator==(const RingBuffer& rhs) const
    {
        return m_data == rhs.m_data
            && m_size == rhs.m_size
            && m_back == rhs.m_back
            && m_front == rhs.m_front;
    }

    // 要素を追加
    template <typename T>
    void RingBuffer<T>::push_back(const value_type& value)
    {
        if (full()) {
            m_front = get_next_pos(m_front);
        }
        else {
            m_size++;
        }

        m_data[m_back] = value;
        m_back = get_next_pos(m_back);
    }

    // 要素を取り除く
    template <typename T>
    void RingBuffer<T>::pop_front()
    {
        assert(empty() == false);

        m_front = get_next_pos(m_front);
        --m_size;
    }

    // 空にする
    template <typename T>
    void RingBuffer<T>::clear()
    {
        m_size = 0;
        m_back = 0;
        m_front = 0;
    }

}

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

int main()
{
    mylib::RingBuffer<int> rb1 {8};
    mylib::RingBuffer<int> rb2 {4};

    for (int i = 0; i < 10; ++i) {
        rb1.push_back(i);
        std::cout << rb1.back() << "\n";
    }

    rb2 = rb1;
    std::cout << std::boolalpha
              << "rb1 == rb2 --> " << (rb1 == rb2) << "\n"
              << "rb1 != rb2 --> " << (rb1 != rb2) << "\n";

    while (rb1.empty() == false) {
        auto v = rb1.front();
        rb1.pop_front();
        std::cout << "v = " << v << "\n";
    }

    std::cout << "rb1 size == " << rb1.size() << "\n";
    std::cout << "rb2 size == " << rb2.size() << "\n";
}

実行結果:

0
1
2
3
4
5
6
7
8
9
rb1 == rb2 --> true
rb1 != rb2 --> false
v = 2
v = 3
v = 4
v = 5
v = 6
v = 7
v = 8
v = 9
rb1 size == 0
rb2 size == 8

問題4 (応用★★★) 🔗

RingBufferクラステンプレートに、std::vector などのコンテナにあるようなイテレータの機能を実装してください。要件は以下のとおりです。


まず、iterator という型を定義します。イテレータは要素を指し示せるだけの情報をもったオブジェクトですから、これ自体もクラスにします。

    template <typename T>
    class RingBuffer {
    public:
        // イテレータ
        class Iterator {
        public:
            // コンストラクタ
            Iterator(RingBuffer& body, size_type pos, bool is_past_the_end);

        private:
            RingBuffer&             m_body;             // 本体のリングバッファへの参照
            size_type               m_pos;              // 指し示している要素の位置
            bool                    m_is_past_the_end;  // 終端の次を指すイテレータか
        };
    };

    // コンストラクタ
    template <typename T>
    RingBuffer<T>::Iterator::Iterator(RingBuffer& body, size_type pos, bool is_past_the_end) :
        m_body {body},
        m_pos {pos},
        m_is_past_the_end {is_past_the_end}
    {

    }

本体のリングバッファにアクセスできるように参照なりポインタなりが必要です。どの要素を指し示しているかも渡せるようにしておきます。また、リングバッファなので、要素の位置の情報だけでは、past-the-endイテレータ(末尾の要素の後ろを指すイテレータ)が判断できません(末尾の次の位置には、先頭の要素があるので)。そこで、past-the-endイテレータなのかどうかも渡せるようにします。

次に、イテレータを返す beginメンバ関数と endメンバ関数を用意します。iterator オブジェクトを生成して返すだけです。

    // リングバッファ
    template <typename T>
    class RingBuffer {
    public:
        // 先頭の要素を指す イテレータを返す
        inline iterator begin()
        {
            return iterator(*this, m_front, empty());
        }

        // 末尾の要素の次を指す イテレータを返す
        inline iterator end()
        {
            return iterator(*this, m_back, true);
        }
    };

Iteratorクラスに、各種演算子のオーバーロードを実装します。

    template <typename T>
    class RingBuffer {
    public:
        // イテレータ
        class Iterator {
        public:
            using value_type = T;                       // 要素型
            using reference = value_type&;              // 要素の参照型
            using const_reference = const value_type&;  // 要素の参照型
            using pointer = value_type*;                // 要素のポインタ型
            using const_pointer = const value_type*;    // 要素の constポインタ型
            using size_type = std::size_t;              // サイズ型
            using difference_type = std::ptrdiff_t;     // 距離型

        public:
            // ==演算子
            inline bool operator==(const Iterator& rhs) const
            {
                return m_body == rhs.m_body
                    && m_pos == rhs.m_pos
                    && m_is_past_the_end == rhs.m_is_past_the_end;
            }

            // !=演算子
            inline bool operator!=(const Iterator& rhs) const
            {
                return !(*this == rhs);
            }

            // *演算子(間接参照)
            inline reference operator*()
            {
                return *common_get_elem_ptr(this);
            }

            // *演算子(間接参照)
            inline const_reference operator*() const
            {
                return *common_get_elem_ptr(this);
            }

            // ->演算子
            inline pointer operator->()
            {
                return common_get_elem_ptr(this);
            }

            // ->演算子
            inline const_pointer operator->() const
            {
                return common_get_elem_ptr(this);
            }

            // ++演算子(前置)
            Iterator& operator++();

            // ++演算子(後置)
            Iterator operator++(int);


        private:
            template <typename T>
            inline static auto* common_get_elem_ptr(T* self)
            {
                return &self->m_body.m_data[self->m_pos];
            }
        };
    };

    // ++演算子(前置)
    template <typename T>
    typename RingBuffer<T>::Iterator& RingBuffer<T>::Iterator::operator++()
    {
        assert(!m_is_past_the_end);

        m_pos = m_body.get_next_pos(m_pos);

        // 終端要素の位置を越えた?
        if (m_body.get_next_pos(m_pos) == m_body.get_next_pos(m_body.end().m_pos)) {
            m_is_past_the_end = true;
        }

        return *this;
    }

    // ++演算子(後置)
    template <typename T>
    typename RingBuffer<T>::Iterator RingBuffer<T>::Iterator::operator++(int)
    {
        Iterator tmp {*this};
        ++(*this);
        return tmp;
    }

プログラム全体としては次のようになります。

// ring_buffer.h
#ifndef RING_BUFFER_H_INCLUDED
#define RING_BUFFER_H_INCLUDED

#include <algorithm>
#include <cassert>
#include <vector>

namespace mylib {

    // リングバッファ
    template <typename T>
    class RingBuffer {

        template <typename T>
        friend class RingBuffer;

    public:
        // イテレータ
        class Iterator {
        public:
            using value_type = T;                       // 要素型
            using reference = value_type&;              // 要素の参照型
            using const_reference = const value_type&;  // 要素の参照型
            using pointer = value_type*;                // 要素のポインタ型
            using const_pointer = const value_type*;    // 要素の constポインタ型
            using size_type = std::size_t;              // サイズ型
            using difference_type = std::ptrdiff_t;     // 距離型

        public:
            // コンストラクタ
            Iterator(RingBuffer& body, size_type pos, bool is_past_the_end);


            // ==演算子
            inline bool operator==(const Iterator& rhs) const
            {
                return m_body == rhs.m_body
                    && m_pos == rhs.m_pos
                    && m_is_past_the_end == rhs.m_is_past_the_end;
            }

            // !=演算子
            inline bool operator!=(const Iterator& rhs) const
            {
                return !(*this == rhs);
            }

            // *演算子(間接参照)
            inline reference operator*()
            {
                return *common_get_elem_ptr(this);
            }

            // *演算子(間接参照)
            inline const_reference operator*() const
            {
                return *common_get_elem_ptr(this);
            }

            // ->演算子
            inline pointer operator->()
            {
                return common_get_elem_ptr(this);
            }

            // ->演算子
            inline const_pointer operator->() const
            {
                return common_get_elem_ptr(this);
            }

            // ++演算子(前置)
            Iterator& operator++();

            // ++演算子(後置)
            Iterator operator++(int);


        private:
            template <typename T>
            inline static auto* common_get_elem_ptr(T* self)
            {
                return &self->m_body.m_data[self->m_pos];
            }

        private:
            RingBuffer&             m_body;             // 本体のリングバッファへの参照
            size_type               m_pos;              // 指し示している要素の位置
            bool                    m_is_past_the_end;  // 終端の次を指すイテレータか
        };

    public:
        using container_type = typename std::vector<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 pointer = typename container_type::pointer;                   // 要素のポインタ型
        using const_pointer = typename container_type::const_pointer;       // 要素の constポインタ型
        using size_type = typename container_type::size_type;               // サイズ型
        using iterator = Iterator;                                          // イテレータ型

    public:
        // コンストラクタ
        //
        // size: 容量
        explicit RingBuffer(size_type capacity);

        // テンプレート変換コンストラクタ
        template <typename U>
        RingBuffer(const RingBuffer<U>& other);


        // コピー代入演算子
        RingBuffer& operator=(const RingBuffer& rhs) = default;
        
        // ==演算子
        bool operator==(const RingBuffer& rhs) const;

        // !=演算子
        inline bool operator!=(const RingBuffer& rhs) const
        {
            return !(*this == rhs);
        }


        // 要素を追加
        void push_back(const value_type& value);

        // 要素を取り除く
        void pop_front();

        // 空にする
        void clear();


        // 先頭の要素の参照を返す
        inline reference front()
        {
            return common_front(this);
        }

        // 先頭の要素の参照を返す
        inline const_reference front() const
        {
            return common_front(this);
        }

        // 末尾の要素の参照を返す
        inline reference back()
        {
            return common_back(this);
        }

        // 末尾の要素の参照を返す
        inline const_reference back() const
        {
            return common_back(this);
        }



        // 先頭の要素を指す イテレータを返す
        inline iterator begin()
        {
            return iterator(*this, m_front, empty());
        }

        // 末尾の要素の次を指す イテレータを返す
        inline iterator end()
        {
            return iterator(*this, m_back, true);
        }


        // 容量を返す
        inline size_type capacity() const
        {
            return m_data.size();
        }

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

        // 空かどうかを返す
        inline bool empty() const
        {
            return m_size == 0;
        }

        // 満杯かどうかを返す
        inline bool full() const
        {
            return m_size == capacity();
        }

    private:
        // 次の位置を返す
        inline size_type get_next_pos(size_type pos) const
        {
            return (pos + 1) % capacity();
        }

        // 手前の位置を返す
        inline size_type get_prev_pos(size_type pos) const
        {
            if (pos >= 1) {
                return pos - 1;
            }
            else {
                return m_size - 1;
            }
        }

        template <typename T>
        inline static auto& common_front(T* self)
        {
            assert(self->empty() == false);
            return self->m_data[self->m_front];
        }

        template <typename T>
        inline static auto& common_back(T* self)
        {
            assert(self->empty() == false);
            return self->m_data[self->get_prev_pos(self->m_back)];
        }

    private:
        container_type              m_data;         // 要素
        size_type                   m_size{0};      // 有効な要素の個数
        size_type                   m_back{0};      // 次に push される位置
        size_type                   m_front{0};     // 次に pop される位置
    };


    // コンストラクタ
    template <typename T>
    RingBuffer<T>::RingBuffer(size_type capacity) : m_data(capacity)
    {
    }

    // コンストラクタ(異なる要素型の RingBuffer から作成)
    template <typename T>
    template <typename U>
    RingBuffer<T>::RingBuffer(const RingBuffer<U>& other) :
        m_data(other.m_data.capacity()),
        m_size {other.m_size},
        m_back {other.m_back},
        m_front {other.m_front}
    {
        std::transform(
            std::cbegin(other.m_data),
            std::cend(other.m_data),
            std::begin(m_data),
            [](const auto& e) {
                return static_cast<T>(e);
            }
        );
    }

    // ==演算子
    template <typename T>
    bool RingBuffer<T>::operator==(const RingBuffer& rhs) const
    {
        return m_data == rhs.m_data
            && m_size == rhs.m_size
            && m_back == rhs.m_back
            && m_front == rhs.m_front;
    }

    // 要素を追加
    template <typename T>
    void RingBuffer<T>::push_back(const value_type& value)
    {
        if (full()) {
            m_front = get_next_pos(m_front);
        }
        else {
            m_size++;
        }

        m_data[m_back] = value;
        m_back = get_next_pos(m_back);
    }

    // 要素を取り除く
    template <typename T>
    void RingBuffer<T>::pop_front()
    {
        assert(empty() == false);

        m_front = get_next_pos(m_front);
        --m_size;
    }

    // 空にする
    template <typename T>
    void RingBuffer<T>::clear()
    {
        m_size = 0;
        m_back = 0;
        m_front = 0;
    }




    // ------------ Iterator ------------
    // コンストラクタ
    template <typename T>
    RingBuffer<T>::Iterator::Iterator(RingBuffer& body, size_type pos, bool is_past_the_end) :
        m_body {body},
        m_pos {pos},
        m_is_past_the_end {is_past_the_end}
    {

    }

    // ++演算子(前置)
    template <typename T>
    typename RingBuffer<T>::Iterator& RingBuffer<T>::Iterator::operator++()
    {
        assert(!m_is_past_the_end);

        m_pos = m_body.get_next_pos(m_pos);

        // 終端要素の位置を越えた?
        if (m_body.get_next_pos(m_pos) == m_body.get_next_pos(m_body.end().m_pos)) {
            m_is_past_the_end = true;
        }

        return *this;
    }

    // ++演算子(後置)
    template <typename T>
    typename RingBuffer<T>::Iterator RingBuffer<T>::Iterator::operator++(int)
    {
        Iterator tmp {*this};
        ++(*this);
        return tmp;
    }
}

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

int main()
{
    mylib::RingBuffer<int> rb {8};

    for (int i = 0; i < 10; ++i) {
        rb.push_back(i);
    }

    for (auto it = rb.begin(); it != rb.end(); ++it) {
        *it *= 10;
    }

    for (auto it = rb.begin(); it != rb.end(); ++it) {
        std::cout << *it << "\n";
    }
}

実行結果:

20
30
40
50
60
70
80
90

std::begin関数や std::end関数は、std::begin(it)it.begin() に、std::end(it)it.end() に変換しているだけなので、beginメンバ関数、endメンバ関数が実装できていると、std::begin関数や std::end関数が自動的に使えるようになります。さらに、範囲for文も使えるようになります。

//main.cpp
#include <iostream>
#include "ring_buffer.h"

int main()
{
    mylib::RingBuffer<int> rb {8};

    for (int i = 0; i < 10; ++i) {
        rb.push_back(i);
    }

    for (auto it = std::begin(rb); it != std::end(rb); ++it) {
        *it *= 10;
    }

    for (const auto& v : rb) {
        std::cout << v << "\n";
    }
}

実行結果:

20
30
40
50
60
70
80
90

問題5 (応用★★★) 🔗

RingBufferクラステンプレートに、constイテレータを実装してください。


問題4の実装の時点では、beginメンバ関数と endメンバ関数が非constメンバ関数になっているため、RingBuffer のオブジェクトが const の場合にイテレータが使えません。また、std::cbegin関数や std::cend関数を使うこともできません。

//main.cpp
#include <iostream>
#include "ring_buffer.h"

int main()
{
    mylib::RingBuffer<int> rb {8};

    for (int i = 0; i < 10; ++i) {
        rb.push_back(i);
    }

    const mylib::RingBuffer<int> crb {rb};

    for (auto it = std::begin(rb); it != std::end(rb); ++it) {
        *it *= 10;
    }

    for (const auto& v : rb) {
        std::cout << v << "\n";
    }

    for (const auto& v : crb) {  // コンパイルエラー
        std::cout << v << "\n";
    }
    for (auto it = std::cbegin(crb); it != std::cend(crb); ++it) {  // コンパイルエラー
        std::cout << *it << "\n";
    }
}

std::cbegin関数や std::cend関数が呼び出すものは、constメンバ関数版の beginメンバ関数や endメンバ関数です。

そこで、beginメンバ関数と endメンバ関数に constメンバ関数版を追加します。戻り値の型として const_iterator を導入します。

    template <typename T>
    class RingBuffer {
    public:
        using const_iterator = ConstIterator;   // constイテレータ型


        // 先頭の要素を指す constイテレータを返す
        inline const_iterator begin() const
        {
            return const_iterator(*this, m_front, empty());
        }

        // 末尾の要素の次を指す constイテレータを返す
        inline const_iterator end() const
        {
            return const_iterator(*this, m_back, true);
        }
    };

メンバの内容には違いがあるため、Iterator と ConstIterator は別々のクラステンプレートとして定義することになります。基本的には Iterator のコードをそのまま複製し、非const な部分を const に直す対応をすればいいだけです。

    template <typename T>
    class RingBuffer {
    public:
        // constイテレータ
        class ConstIterator {
        public:
            using value_type = T;                       // 要素型
            using const_reference = const value_type&;  // 要素の参照型
            using const_pointer = const value_type*;    // 要素の constポインタ型
            using size_type = std::size_t;              // サイズ型
            using difference_type = std::ptrdiff_t;     // 距離型

        public:
            // コンストラクタ
            ConstIterator(const RingBuffer& body, size_type pos, bool is_past_the_end);


            // ==演算子
            inline bool operator==(const ConstIterator& rhs) const
            {
                return m_body == rhs.m_body
                    && m_pos == rhs.m_pos
                    && m_is_past_the_end == rhs.m_is_past_the_end;
            }

            // !=演算子
            inline bool operator!=(const ConstIterator& rhs) const
            {
                return !(*this == rhs);
            }

            // *演算子(間接参照)
            inline const_reference operator*() const
            {
                return *common_get_elem_ptr(this);
            }

            // ->演算子
            inline const_pointer operator->() const
            {
                return common_get_elem_ptr(this);
            }

            // ++演算子(前置)
            ConstIterator& operator++();

            // ++演算子(後置)
            ConstIterator operator++(int);


        private:
            template <typename T>
            inline static auto* common_get_elem_ptr(T* self)
            {
                return &self->m_body.m_data[self->m_pos];
            }

        private:
            const RingBuffer&       m_body;             // 本体のリングバッファへの参照
            size_type               m_pos;              // 指し示している要素の位置
            bool                    m_is_past_the_end;  // 終端の次を指すイテレータか
        };
    };


    // コンストラクタ
    template <typename T>
    RingBuffer<T>::ConstIterator::ConstIterator(const RingBuffer& body, size_type pos, bool is_past_the_end) :
        m_body {body},
        m_pos {pos},
        m_is_past_the_end {is_past_the_end}
    {

    }

    // ++演算子(前置)
    template <typename T>
    typename RingBuffer<T>::ConstIterator& RingBuffer<T>::ConstIterator::operator++()
    {
        assert(!m_is_past_the_end);

        m_pos = m_body.get_next_pos(m_pos);

        // 終端要素の位置を越えた?
        if (m_body.get_next_pos(m_pos) == m_body.get_next_pos(m_body.end().m_pos)) {
            m_is_past_the_end = true;
        }

        return *this;
    }

    // ++演算子(後置)
    template <typename T>
    typename RingBuffer<T>::ConstIterator RingBuffer<T>::ConstIterator::operator++(int)
    {
        Iterator tmp {*this};
        ++(*this);
        return tmp;
    }

プログラム全体としては次のようになります。

// ring_buffer.h
#ifndef RING_BUFFER_H_INCLUDED
#define RING_BUFFER_H_INCLUDED

#include <algorithm>
#include <cassert>
#include <vector>

namespace mylib {

    // リングバッファ
    template <typename T>
    class RingBuffer {

        template <typename T>
        friend class RingBuffer;

    public:
        // イテレータ
        class Iterator {
        public:
            using value_type = T;                       // 要素型
            using reference = value_type&;              // 要素の参照型
            using const_reference = const value_type&;  // 要素の参照型
            using pointer = value_type*;                // 要素のポインタ型
            using const_pointer = const value_type*;    // 要素の constポインタ型
            using size_type = std::size_t;              // サイズ型
            using difference_type = std::ptrdiff_t;     // 距離型

        public:
            // コンストラクタ
            Iterator(RingBuffer& body, size_type pos, bool is_past_the_end);


            // ==演算子
            inline bool operator==(const Iterator& rhs) const
            {
                return m_body == rhs.m_body
                    && m_pos == rhs.m_pos
                    && m_is_past_the_end == rhs.m_is_past_the_end;
            }

            // !=演算子
            inline bool operator!=(const Iterator& rhs) const
            {
                return !(*this == rhs);
            }

            // *演算子(間接参照)
            inline reference operator*()
            {
                return *common_get_elem_ptr(this);
            }

            // *演算子(間接参照)
            inline const_reference operator*() const
            {
                return *common_get_elem_ptr(this);
            }

            // ->演算子
            inline pointer operator->()
            {
                return common_get_elem_ptr(this);
            }

            // ->演算子
            inline const_pointer operator->() const
            {
                return common_get_elem_ptr(this);
            }

            // ++演算子(前置)
            Iterator& operator++();

            // ++演算子(後置)
            Iterator operator++(int);


        private:
            template <typename T>
            inline static auto* common_get_elem_ptr(T* self)
            {
                return &self->m_body.m_data[self->m_pos];
            }

        private:
            RingBuffer&             m_body;             // 本体のリングバッファへの参照
            size_type               m_pos;              // 指し示している要素の位置
            bool                    m_is_past_the_end;  // 終端の次を指すイテレータか
        };

        // constイテレータ
        class ConstIterator {
        public:
            using value_type = T;                       // 要素型
            using const_reference = const value_type&;  // 要素の参照型
            using const_pointer = const value_type*;    // 要素の constポインタ型
            using size_type = std::size_t;              // サイズ型
            using difference_type = std::ptrdiff_t;     // 距離型

        public:
            // コンストラクタ
            ConstIterator(const RingBuffer& body, size_type pos, bool is_past_the_end);


            // ==演算子
            inline bool operator==(const ConstIterator& rhs) const
            {
                return m_body == rhs.m_body
                    && m_pos == rhs.m_pos
                    && m_is_past_the_end == rhs.m_is_past_the_end;
            }

            // !=演算子
            inline bool operator!=(const ConstIterator& rhs) const
            {
                return !(*this == rhs);
            }

            // *演算子(間接参照)
            inline const_reference operator*() const
            {
                return *common_get_elem_ptr(this);
            }

            // ->演算子
            inline const_pointer operator->() const
            {
                return common_get_elem_ptr(this);
            }

            // ++演算子(前置)
            ConstIterator& operator++();

            // ++演算子(後置)
            ConstIterator operator++(int);


        private:
            template <typename T>
            inline static auto* common_get_elem_ptr(T* self)
            {
                return &self->m_body.m_data[self->m_pos];
            }

        private:
            const RingBuffer&       m_body;             // 本体のリングバッファへの参照
            size_type               m_pos;              // 指し示している要素の位置
            bool                    m_is_past_the_end;  // 終端の次を指すイテレータか
        };

    public:
        using container_type = typename std::vector<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 pointer = typename container_type::pointer;                   // 要素のポインタ型
        using const_pointer = typename container_type::const_pointer;       // 要素の constポインタ型
        using size_type = typename container_type::size_type;               // サイズ型
        using iterator = Iterator;                                          // イテレータ型
        using const_iterator = ConstIterator;                               // constイテレータ型

    public:
        // コンストラクタ
        //
        // size: 容量
        explicit RingBuffer(size_type capacity);

        // テンプレート変換コンストラクタ
        template <typename U>
        RingBuffer(const RingBuffer<U>& other);


        // コピー代入演算子
        RingBuffer& operator=(const RingBuffer& rhs) = default;
        
        // ==演算子
        bool operator==(const RingBuffer& rhs) const;

        // !=演算子
        inline bool operator!=(const RingBuffer& rhs) const
        {
            return !(*this == rhs);
        }


        // 要素を追加
        void push_back(const value_type& value);

        // 要素を取り除く
        void pop_front();

        // 空にする
        void clear();


        // 先頭の要素の参照を返す
        inline reference front()
        {
            return common_front(this);
        }

        // 先頭の要素の参照を返す
        inline const_reference front() const
        {
            return common_front(this);
        }

        // 末尾の要素の参照を返す
        inline reference back()
        {
            return common_back(this);
        }

        // 末尾の要素の参照を返す
        inline const_reference back() const
        {
            return common_back(this);
        }



        // 先頭の要素を指す イテレータを返す
        inline iterator begin()
        {
            return iterator(*this, m_front, empty());
        }

        // 末尾の要素の次を指す イテレータを返す
        inline iterator end()
        {
            return iterator(*this, m_back, true);
        }

        // 先頭の要素を指す constイテレータを返す
        inline const_iterator begin() const
        {
            return const_iterator(*this, m_front, empty());
        }

        // 末尾の要素の次を指す constイテレータを返す
        inline const_iterator end() const
        {
            return const_iterator(*this, m_back, true);
        }


        // 容量を返す
        inline size_type capacity() const
        {
            return m_data.size();
        }

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

        // 空かどうかを返す
        inline bool empty() const
        {
            return m_size == 0;
        }

        // 満杯かどうかを返す
        inline bool full() const
        {
            return m_size == capacity();
        }

    private:
        // 次の位置を返す
        inline size_type get_next_pos(size_type pos) const
        {
            return (pos + 1) % capacity();
        }

        // 手前の位置を返す
        inline size_type get_prev_pos(size_type pos) const
        {
            if (pos >= 1) {
                return pos - 1;
            }
            else {
                return m_size - 1;
            }
        }

        template <typename T>
        inline static auto& common_front(T* self)
        {
            assert(self->empty() == false);
            return self->m_data[self->m_front];
        }

        template <typename T>
        inline static auto& common_back(T* self)
        {
            assert(self->empty() == false);
            return self->m_data[self->get_prev_pos(self->m_back)];
        }

    private:
        container_type              m_data;         // 要素
        size_type                   m_size{0};      // 有効な要素の個数
        size_type                   m_back{0};      // 次に push される位置
        size_type                   m_front{0};     // 次に pop される位置
    };


    // コンストラクタ
    template <typename T>
    RingBuffer<T>::RingBuffer(size_type capacity) : m_data(capacity)
    {
    }

    // コンストラクタ(異なる要素型の RingBuffer から作成)
    template <typename T>
    template <typename U>
    RingBuffer<T>::RingBuffer(const RingBuffer<U>& other) :
        m_data(other.m_data.capacity()),
        m_size {other.m_size},
        m_back {other.m_back},
        m_front {other.m_front}
    {
        std::transform(
            std::cbegin(other.m_data),
            std::cend(other.m_data),
            std::begin(m_data),
            [](const auto& e) {
                return static_cast<T>(e);
            }
        );
    }

    // ==演算子
    template <typename T>
    bool RingBuffer<T>::operator==(const RingBuffer& rhs) const
    {
        return m_data == rhs.m_data
            && m_size == rhs.m_size
            && m_back == rhs.m_back
            && m_front == rhs.m_front;
    }

    // 要素を追加
    template <typename T>
    void RingBuffer<T>::push_back(const value_type& value)
    {
        if (full()) {
            m_front = get_next_pos(m_front);
        }
        else {
            m_size++;
        }

        m_data[m_back] = value;
        m_back = get_next_pos(m_back);
    }

    // 要素を取り除く
    template <typename T>
    void RingBuffer<T>::pop_front()
    {
        assert(empty() == false);

        m_front = get_next_pos(m_front);
        --m_size;
    }

    // 空にする
    template <typename T>
    void RingBuffer<T>::clear()
    {
        m_size = 0;
        m_back = 0;
        m_front = 0;
    }




    // ------------ Iterator ------------
    // コンストラクタ
    template <typename T>
    RingBuffer<T>::Iterator::Iterator(RingBuffer& body, size_type pos, bool is_past_the_end) :
        m_body {body},
        m_pos {pos},
        m_is_past_the_end {is_past_the_end}
    {

    }

    // ++演算子(前置)
    template <typename T>
    typename RingBuffer<T>::Iterator& RingBuffer<T>::Iterator::operator++()
    {
        assert(!m_is_past_the_end);

        m_pos = m_body.get_next_pos(m_pos);

        // 終端要素の位置を越えた?
        if (m_body.get_next_pos(m_pos) == m_body.get_next_pos(m_body.end().m_pos)) {
            m_is_past_the_end = true;
        }

        return *this;
    }

    // ++演算子(後置)
    template <typename T>
    typename RingBuffer<T>::Iterator RingBuffer<T>::Iterator::operator++(int)
    {
        Iterator tmp {*this};
        ++(*this);
        return tmp;
    }


    // ------------ ConstIterator ------------
    // コンストラクタ
    template <typename T>
    RingBuffer<T>::ConstIterator::ConstIterator(const RingBuffer& body, size_type pos, bool is_past_the_end) :
        m_body {body},
        m_pos {pos},
        m_is_past_the_end {is_past_the_end}
    {

    }

    // ++演算子(前置)
    template <typename T>
    typename RingBuffer<T>::ConstIterator& RingBuffer<T>::ConstIterator::operator++()
    {
        assert(!m_is_past_the_end);

        m_pos = m_body.get_next_pos(m_pos);

        // 終端要素の位置を越えた?
        if (m_body.get_next_pos(m_pos) == m_body.get_next_pos(m_body.end().m_pos)) {
            m_is_past_the_end = true;
        }

        return *this;
    }

    // ++演算子(後置)
    template <typename T>
    typename RingBuffer<T>::ConstIterator RingBuffer<T>::ConstIterator::operator++(int)
    {
        Iterator tmp {*this};
        ++(*this);
        return tmp;
    }
}

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

int main()
{
    mylib::RingBuffer<int> rb {8};

    for (int i = 0; i < 10; ++i) {
        rb.push_back(i);
    }

    const mylib::RingBuffer<int> crb {rb};

    for (auto it = std::begin(rb); it != std::end(rb); ++it) {
        *it *= 10;
    }

    for (const auto& v : crb) {
        std::cout << v << "\n";
    }

    for (auto it = std::cbegin(crb); it != std::cend(crb); ++it) {
        std::cout << *it << "\n";
    }
}

実行結果:

2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9


参考リンク 🔗



更新履歴 🔗




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