C++03のstd::queue
とstd::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()
が例外を投げる可能性があるか調べ中。