読者です 読者をやめる 読者になる 読者になる

ムーブによるpop

C++

C++03のstd::queuestd::stackがもつpop()メンバ関数は、要素型Tのコピーコンストラクタが例外を投げた場合に、削除がすでに済んでいるせいでコンテナが壊れてしまう、という例外安全性の問題があるために戻り値の型がvoidになっていました。
C++11にはムーブとnoexceptがあるので、要素型Tが例外を投げないムーブコンストラクタを持っていれば、戻り値を返すpop()が書けるのではないかと思います。


書いてみました。

#include <queue>
#include <type_traits>

template <class T, class Container = std::deque<T>>
class movable_queue : public std::queue<T, Container> {
    using base = std::queue<T, Container>;
public:
    T move_pop()
    {
        static_assert(std::is_nothrow_move_constructible<T>{},
                      "T must be nothrow move constructible");

        T x = std::move(base::front());
        base::pop();
        return x;
    }
};

#include <iostream>
#include <memory>
#include <string>

struct X {
    X() = default;
    X(X&&) noexcept(false) {}
};

int main()
{
    // non copyable, movable
    {
        movable_queue<std::unique_ptr<int>> que;

        que.push(std::unique_ptr<int>(new int(3)));
        que.push(std::unique_ptr<int>(new int(1)));
        que.push(std::unique_ptr<int>(new int(4)));

        while (!que.empty()) {
            std::unique_ptr<int> p = que.move_pop();
            std::cout << *p << std::endl;
        }
    }

    // basic_string has nothrow move constructor
    {
        movable_queue<std::string> que;

        que.push("aaa");
        que.push("bbb");
        que.push("ccc");

        while (!que.empty()) {
            std::string s = que.move_pop();
            std::cout << s << std::endl;
        }
    }

    // X hasn't nothrow move constructor
    {
        movable_queue<X> que;

        que.push(X());
        que.push(X());
        que.push(X());

//      while (!que.empty()) {
//          X x = que.move_pop(); // Error!!!
//      }
    }
}
3
1
4
aaa
bbb
ccc

std::queueを継承して作ったmovable_queue(名前がちょっと変ですが)には、move_pop()というメンバ関数を追加しています。
この関数は、先頭要素をムーブによって取得したあと先頭要素を削除し、保持しておいた先頭要素をムーブによって返します(return文は自動的にムーブします)。要素型Tが例外を投げないムーブコンストラクタを持っていなければ、この関数は呼べないようにしておきました。


問題ないように見えますが、まだ懸念事項を解決しきっていません。front()呼び出しの後にpop()が例外を投げると、やはり壊れてしまいます。キューが空であればfront()の時点でアクセス違反になる(未規定)ので、pop()まで来ないから問題ありません。それ以外で、pop()が例外を投げる可能性があるか調べ中。