C++ で vector を結合する方法を紹介します。
コピーで vector を結合する
まずは要素のコピーで vector を結合する方法です。
vector の要素が次に該当する場合は、この手法を用いるのが良いです。
- 要素の型が shared_ptr である場合
- 要素が軽量であり、かつコピー可である場合(例えば int や float など)
insert メソッド(要素のコピー)
vector クラスの insert メソッドで結合する例です。
v1 の末尾に v2 の各要素をコピーします。
#include <iostream>
#include <vector>
int main() {
std::vector<int> v1{0, 1, 2};
std::vector<int> v2{3, 4};
v1.insert(v1.end(), v2.begin(), v2.end());
for (auto item : v1) {
std::cout << item << ", ";
}
std::cout << std::endl;
// output
// 0, 1, 2, 3, 4,
return 0;
}
std::copy(要素のコピー)
std::copy
を用いることでも同様の効果を得ることができます。
前の例と同じように、v1 の末尾に v2 の要素がコピーされます。
#include <iostream>
#include <vector>
int main() {
std::vector<int> v1{0, 1, 2};
std::vector<int> v2{3, 4};
std::copy(v2.begin(), v2.end(), std::back_inserter(v1));
for (auto item : v1) {
std::cout << item << ", ";
}
std::cout << std::endl;
// output
// 0, 1, 2, 3, 4,
return 0;
}
std::merge(要素のコピー)
std::merge
を用いると、v1, v2 の状態を保全したまま新規の vector を生成することができます。
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> v1{0, 1, 2};
std::vector<int> v2{3, 4};
std::vector<int> v3;
std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(v3));
for (auto item : v3) {
std::cout << item << ", ";
}
std::cout << std::endl;
// output
// 0, 1, 2, 3, 4,
return 0;
}
ムーブで vector を結合する
コピーの例ではいくつか問題が発生することがあります。
vector の要素のサイズが大きい場合はパフォーマンスの問題が発生し得ます。
コピー不可の型が要素である場合には、そもそもコンパイルでエラーが発生してしまいます。
例えば、unique_ptr(コピー不可の型)に対して insert を行った場合、g++ のコンパイルではおびただしい量のエラーメッセージが出力されます。
#include <iostream>
#include <memory>
#include <vector>
int main() {
std::vector<std::unique_ptr<int>> v1;
v1.push_back(std::make_unique<int>(0));
v1.push_back(std::make_unique<int>(1));
v1.push_back(std::make_unique<int>(2));
std::vector<std::unique_ptr<int>> v2;
v2.push_back(std::make_unique<int>(3));
v2.push_back(std::make_unique<int>(4));
v1.insert(v1.end(), v2.begin(), v2.end());
return 0;
}
$ g++ main_move.cc
In file included from /usr/include/c++/11/memory:66,
from main_move.cc:2:
/usr/include/c++/11/bits/stl_uninitialized.h: In instantiation of ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _ForwardIterator = std::unique_ptr<int>*]’:
/usr/include/c++/11/bits/stl_uninitialized.h:333:37: required from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _ForwardIterator = std::unique_ptr<int>*; _Tp = std::unique_ptr<int>]’
/usr/include/c++/11/bits/vector.tcc:751:34: required from ‘void std::vector<_Tp, _Alloc>::_M_range_insert(std::vector<_Tp, _Alloc>::iterator, _ForwardIterator, _ForwardIterator, std::forward_iterator_tag) [with _ForwardIterator = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _Tp = std::unique_ptr<int>; _Alloc = std::allocator<std::unique_ptr<int> >; std::vector<_Tp, _Alloc>::iterator = std::vector<std::unique_ptr<int> >::iterator]’
/usr/include/c++/11/bits/stl_vector.h:1665:19: required from ‘void std::vector<_Tp, _Alloc>::_M_insert_dispatch(std::vector<_Tp, _Alloc>::iterator, _InputIterator, _InputIterator, std::__false_type) [with _InputIterator = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _Tp = std::unique_ptr<int>; _Alloc = std::allocator<std::unique_ptr<int> >; std::vector<_Tp, _Alloc>::iterator = std::vector<std::unique_ptr<int> >::iterator]’
/usr/include/c++/11/bits/stl_vector.h:1383:22: required from ‘std::vector<_Tp, _Alloc>::iterator std::vector<_Tp, _Alloc>::insert(std::vector<_Tp, _Alloc>::const_iterator, _InputIterator, _InputIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; <template-parameter-2-2> = void; _Tp = std::unique_ptr<int>; _Alloc = std::allocator<std::unique_ptr<int> >; std::vector<_Tp, _Alloc>::iterator = std::vector<std::unique_ptr<int> >::iterator; std::vector<_Tp, _Alloc>::const_iterator = std::vector<std::unique_ptr<int> >::const_iterator]’
main_move.cc:15:14: required from here
/usr/include/c++/11/bits/stl_uninitialized.h:138:72: error: static assertion failed: result type must be constructible from value type of input range
138 | static_assert(is_constructible<_ValueType2, decltype(*__first)>::value,
| ^~~~~
/usr/include/c++/11/bits/stl_uninitialized.h:138:72: note: ‘std::integral_constant<bool, false>::value’ evaluates to false
In file included from /usr/include/c++/11/bits/char_traits.h:39,
from /usr/include/c++/11/ios:40,
from /usr/include/c++/11/ostream:38,
from /usr/include/c++/11/iostream:39,
from main_move.cc:1:
/usr/include/c++/11/bits/stl_algobase.h: In instantiation of ‘static _OI std::__copy_move<false, false, std::random_access_iterator_tag>::__copy_m(_II, _II, _OI) [with _II = std::unique_ptr<int>*; _OI = std::unique_ptr<int>*]’:
/usr/include/c++/11/bits/stl_algobase.h:495:30: required from ‘_OI std::__copy_move_a2(_II, _II, _OI) [with bool _IsMove = false; _II = std::unique_ptr<int>*; _OI = std::unique_ptr<int>*]’
/usr/include/c++/11/bits/stl_algobase.h:522:42: required from ‘_OI std::__copy_move_a1(_II, _II, _OI) [with bool _IsMove = false; _II = std::unique_ptr<int>*; _OI = std::unique_ptr<int>*]’
/usr/include/c++/11/bits/stl_algobase.h:530:31: required from ‘_OI std::__copy_move_a(_II, _II, _OI) [with bool _IsMove = false; _II = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _OI = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >]’
/usr/include/c++/11/bits/stl_algobase.h:620:7: required from ‘_OI std::copy(_II, _II, _OI) [with _II = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _OI = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >]’
/usr/include/c++/11/bits/vector.tcc:744:16: required from ‘void std::vector<_Tp, _Alloc>::_M_range_insert(std::vector<_Tp, _Alloc>::iterator, _ForwardIterator, _ForwardIterator, std::forward_iterator_tag) [with _ForwardIterator = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _Tp = std::unique_ptr<int>; _Alloc = std::allocator<std::unique_ptr<int> >; std::vector<_Tp, _Alloc>::iterator = std::vector<std::unique_ptr<int> >::iterator]’
/usr/include/c++/11/bits/stl_vector.h:1665:19: required from ‘void std::vector<_Tp, _Alloc>::_M_insert_dispatch(std::vector<_Tp, _Alloc>::iterator, _InputIterator, _InputIterator, std::__false_type) [with _InputIterator = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _Tp = std::unique_ptr<int>; _Alloc = std::allocator<std::unique_ptr<int> >; std::vector<_Tp, _Alloc>::iterator = std::vector<std::unique_ptr<int> >::iterator]’
/usr/include/c++/11/bits/stl_vector.h:1383:22: required from ‘std::vector<_Tp, _Alloc>::iterator std::vector<_Tp, _Alloc>::insert(std::vector<_Tp, _Alloc>::const_iterator, _InputIterator, _InputIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; <template-parameter-2-2> = void; _Tp = std::unique_ptr<int>; _Alloc = std::allocator<std::unique_ptr<int> >; std::vector<_Tp, _Alloc>::iterator = std::vector<std::unique_ptr<int> >::iterator; std::vector<_Tp, _Alloc>::const_iterator = std::vector<std::unique_ptr<int> >::const_iterator]’
main_move.cc:15:14: required from here
/usr/include/c++/11/bits/stl_algobase.h:385:25: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’
385 | *__result = *__first;
| ~~~~~~~~~~^~~~~~~~~~
In file included from /usr/include/c++/11/memory:76,
from main_move.cc:2:
/usr/include/c++/11/bits/unique_ptr.h:469:19: note: declared here
469 | unique_ptr& operator=(const unique_ptr&) = delete;
| ^~~~~~~~
この問題を解決するために、値のムーブによって vector を結合する手法を後述します。
insert メソッド(要素のムーブ)
メソッドについては、コピー方式と同じものを利用します。
異なる点として、v2 のイテレータをムーブイテレータに変換します。
具体的には、insert
メソッドの第 2, 3 引数を std::make_move_iterator でラップさせてやります。
#include <iostream>
#include <memory>
#include <vector>
int main() {
std::vector<std::unique_ptr<int>> v1;
v1.push_back(std::make_unique<int>(0));
v1.push_back(std::make_unique<int>(1));
v1.push_back(std::make_unique<int>(2));
std::vector<std::unique_ptr<int>> v2;
v2.push_back(std::make_unique<int>(3));
v2.push_back(std::make_unique<int>(4));
v1.insert(
v1.end(),
std::make_move_iterator(v2.begin()),
std::make_move_iterator(v2.end()));
for (const auto &item : v1) {
std::cout << *item << ", ";
}
std::cout << std::endl;
// output
// 0, 1, 2, 3, 4,
return 0;
}
注意点として、ムーブした後は v2 の要素は無効な状態として更新されてしまいます。
例えば、次のように insert 後に v2 の要素にアクセスしようとすると Segmentation fault(メモリの不正アクセス)によってプログラムが中断されてしまいます。
#include <iostream>
#include <memory>
#include <vector>
int main() {
std::vector<std::unique_ptr<int>> v1;
v1.push_back(std::make_unique<int>(0));
v1.push_back(std::make_unique<int>(1));
v1.push_back(std::make_unique<int>(2));
std::vector<std::unique_ptr<int>> v2;
v2.push_back(std::make_unique<int>(3));
v2.push_back(std::make_unique<int>(4));
v1.insert(
v1.end(),
std::make_move_iterator(v2.begin()),
std::make_move_iterator(v2.end()));
std::cout << *v2[0] << std::endl;
// Segmentation fault
return 0;
}
std::copy(要素のムーブ)
std::copy
のケースも make_move_iterator
を呼び出すだけで大丈夫です。
insert 同様に、エラーが発生することなく vector の統合が可能です。
#include <iostream>
#include <memory>
#include <vector>
int main() {
std::vector<std::unique_ptr<int>> v1;
v1.push_back(std::make_unique<int>(0));
v1.push_back(std::make_unique<int>(1));
v1.push_back(std::make_unique<int>(2));
std::vector<std::unique_ptr<int>> v2;
v2.push_back(std::make_unique<int>(3));
v2.push_back(std::make_unique<int>(4));
std::copy(
std::make_move_iterator(v2.begin()),
std::make_move_iterator(v2.end()),
std::back_inserter(v1));
for (const auto &item : v1) {
std::cout << *item << ", ";
}
std::cout << std::endl;
// output
// 0, 1, 2, 3, 4,
return 0;
}
std::merge(要素のムーブ)
あえて掲載するまでもないかもしれないですが、std::merge
でムーブを行う例です。
v1 と v2 の要素を v3 にムーブして統合します。
#include <algorithm>
#include <iostream>
#include <memory>
#include <vector>
int main() {
std::vector<std::unique_ptr<int>> v1;
v1.push_back(std::make_unique<int>(0));
v1.push_back(std::make_unique<int>(1));
v1.push_back(std::make_unique<int>(2));
std::vector<std::unique_ptr<int>> v2;
v2.push_back(std::make_unique<int>(3));
v2.push_back(std::make_unique<int>(4));
std::vector<std::unique_ptr<int>> v3;
std::merge(
std::make_move_iterator(v1.begin()),
std::make_move_iterator(v1.end()),
std::make_move_iterator(v2.begin()),
std::make_move_iterator(v2.end()),
std::back_inserter(v3));
for (const auto &item : v3) {
std::cout << *item << ", ";
}
std::cout << std::endl;
// output
// 0, 1, 2, 3, 4,
return 0;
}
コメント