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

トップページ新C++編オーバーロード

このページの概要 🔗

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



解答・解説 🔗

問題1 (確認★) 🔗

次の関数 f1~f8 で、オーバーロードが成功するものとエラーになるものを分けてください。

void f1();
void f1(int a, int b);

int f2(int a);
double f2(int a);

int f3(int a, int b = 0);
int f3(int a = 0, int b = 0);

int f4(int a, int b);
template <typename T>
int f4(T a, T b);

template <typename T>
T f5();
template <typename T>
T f5(T a, T b);

class C1 {
public:
    int f6();
    int f6() const;
};

class C2 {
public:
    int f7();
    static int f7();
};

class C3 {
public:
    int f8();

private:
    int f8(int a, int b);
};


オーバーロード可能かどうかには細かいルールがあります。ごく単純には、関数名が同じで、仮引数などの宣言に違っている部分があり(ただし戻り値の違いだけではだめ)、スコープは同一でなければなりません(本編解説)。

f1 は仮引数に違いがあり、オーバーロードできます。

f2 は戻り値にしか違いがありませんから、オーバーロードできません。

f3 はデフォルト実引数の有無にしか違いがなく、オーバーロードできません。

f4 は関数と関数テンプレートですが、仮引数に違いがありますからオーバーロードできます。

f5 はいずれも関数テンプレートですが、仮引数に違いがありますからオーバーロードできます。

f6 は非constメンバ関数と constメンバ関数の違いだけですが、これはオーバーロードできます。

f7 は非静的メンバ関数と静的メンバ関数の違いだけで、これはオーバーロードできません。

f8 はアクセス指定子に違いがありますが、これはオーバーロードの妨げになりません。仮引数に違いがあるので、オーバーロードできます。

問題2 (基本★) 🔗

以下の関数f の呼び出しが、それぞれ異なる関数を呼び出すように関数f のオーバーロードを作成してください。また、これら以外の型の実引数を渡した場合に呼び出される関数テンプレートf も作成してください。

int main()
{
    int a {100};
    short b {200};
    double c {15.5};

    f(a);
    f(b);
    f(c);
    f(nullptr);
}


関数f に渡している実引数の型はそれぞれ、int、short、double、std::nullptr_t(「構造体とポインタ」のページを参照)です。したがって、これらの型の仮引数を持つ関数をそれぞれ作成すればいいです。さらに、関数テンプレートにした f も作成します。

#include <iostream>

void f(int a)
{
    std::cout << "call f(int)\n";
}

void f(short a)
{
    std::cout << "call f(short)\n";
}

void f(double a)
{
    std::cout << "call f(double)\n";
}

void f(std::nullptr_t a)
{
    std::cout << "call f(std::nullptr_t)\n";
}

template <typename T>
void f(T a)
{
    std::cout << "call f(T)\n";
}



int main()
{
    int a {100};
    short b {200};
    double c {15.5};

    f(a);
    f(b);
    f(c);
    f(nullptr);

    f("xyz");
    f(1000LL);
}

実行結果:

call f(int)
call f(short)
call f(double)
call f(std::nullptr_t)
call f(T)
call f(T)

【C++98/03 経験者】ヌルポインタの表現に 0 や NULL を使うべきでない理由の1つがここにあります。f(0) は当然ながら f(int) を呼び出してしまいます。f(NULL) の場合は NULL の置換結果次第であるため、処理系ごとに結果が異なる恐れがあります(「構造体とポインタ」のページを参照)。

問題3 (応用★★) 🔗

RingBufferクラステンプレートの実装でも、frontメンバ関数と backメンバ関数が、メンバ関数と constメンバ関数の違いによってオーバーロードされています。本体のコードをメンバ関数テンプレートを使って共通化してください。

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


たとえば次のように実装できます。

// 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);


        // 要素を追加
        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>
    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> rb {8};

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

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

実行結果:

0
1
2
3
4
5
6
7
8
9
v = 2
v = 3
v = 4
v = 5
v = 6
v = 7
v = 8
v = 9

constメンバ関数と非constメンバ関数の違いによってオーバーロードするとき、よく本体のコードがまったく同じになります。短く非常に単純なコードなら問題になりませんが、ある程度長いコードや、実装が変わる可能性がありそうな場合は、1か所に共通化しておくことが望ましいといえます。

方法として、const_cast を使うものと、メンバ関数テンプレートを使うものがありますが、ここでは後者を使っています。詳しい方法の説明は(本編解説)を参照してください。


参考リンク 🔗



更新履歴 🔗




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