範囲のコピー・移動 (C++03/C++20)

C++

コレクションを別の場所にコピー・移動する関数の説明です。

概要

配列やコンテナのコピー・移動を行えます。
範囲はシーケンシャルアクセスが必要です。
範囲の指定方法は、
・std::xxx(C++03): 対象の範囲 [first, last) のイテレータ―を指定します。
・std::ranges::xxx(C++20): 対象の範囲 [first, last) のイテレータ―を指定します。
・std::ranges::xxx(C++20): 対象の範囲を直接指定します。
詳細についてはこちらを参照してください。https://mappuri.com/program/cpp20-algorithm/

コピー

copy

範囲をコピーします。

copy_backward

範囲を後ろからコピーします。

前方の一部が重なる範囲にコピーする場合、copy 関数だと重なる範囲が正しくコピーできません。この場合、copy_backword 関数で後ろの要素からコピーすると回避できます。
逆に後方の一部が重なる範囲にコピーする場合は copy 関数を使用します。

copy_if

範囲の条件を満たす要素をコピーします。
比較関数には、より小さい(<)ときに true を返す関数を指定します。
std::ranges::copy_if(C++20): 射影(projection)関数を指定できます。

copy_n

範囲の先頭の指定数の要素をコピーします。

コピーのサンプル (C++03, C++11 iterator)

#include <iostream>

/** r 内の [first, last) の範囲と値を出力 範囲:[0, 9) 値:1 2 3 4 5 4 5 6 6 */
template<class T, class U, class V>
static void output(T&& r, U&& first, V&& last) {
	std::cout << "範囲:[" << std::distance(std::begin(r), first) << ", " << std::distance(std::begin(r), last) << ")";
	std::cout << " 値:";
	for (auto i = first; i != last; ++i) {
		std::cout << *i << " ";
	}
	std::cout << std::endl;
}

#include <algorithm>
#include <iostream>
#include <vector>

int main(int argc, char* argv[]) {
	std::vector<int> r = { 1, 2, 3, 4, 5 };
	std::vector<int> o(r.size());

	// 範囲をコピーします。(C++03)
	auto copy = std::copy(r.begin(), r.end(), o.begin());
	output(o, o.begin(), copy); // 範囲:[0, 5) 値:1 2 3 4 5

	// 範囲を後ろからコピーします。(C++03)
	auto copy_backward = std::copy_backward(r.begin(), r.begin() + 3, r.end());
	output(r, r.begin(), r.end()); // 範囲:[0, 5) 値:1 2 1 2 3 <- 全体
	output(r, copy_backward, r.end()); // 範囲:[2, 5) 値:1 2 3 <- コピー先

	// 範囲の条件を満たす要素をコピーします。(C++11)
	auto copy_if = std::copy_if(r.begin(), r.end(), o.begin(), [](int x) { return x & 1; });
	output(o, o.begin(), copy_if); // 範囲:[0, 3) 値:1 3 5 <- 奇数のみ

	// 範囲の先頭の指定数の要素をコピーします。(C++11)
	auto copy_n = std::copy_n(r.begin(), 3, o.begin());
	output(o, o.begin(), copy_n); // 範囲:[0, 3) 値:1 2 3 <- 先頭3個

	return 0;
}

コピーのサンプル (C++20 range, iterator)

#include <algorithm>
#include <iostream>
#include <vector>

int main(int argc, char* argv[]) {
	std::vector<int> r = { 1, 2, 3, 4, 5 };
	std::vector<int> o(r.size());

	// 範囲をコピーします。(C++20)
	auto copy = std::ranges::copy(r, o.begin());
	output(o, o.begin(), copy.out); // 範囲:[0, 5) 値:1 2 3 4 5

	// 範囲を後ろからコピーします。(C++20)
	auto copy_backward = std::ranges::copy_backward(r.begin(), r.begin() + 3, r.end());
	output(r, r.begin(), r.end()); // 範囲:[0, 5) 値:1 2 1 2 3 <- 全体
	output(r, r.begin(), copy_backward.in); // 範囲:[0, 3) 値:1 2 1 <- コピー元
	output(r, copy_backward.out, r.end()); // 範囲:[2, 5) 値:1 2 3 <- コピー先

	// 範囲の条件を満たす要素をコピーします。(C++20)
	auto copy_if = std::ranges::copy_if(r, o.begin(), [](int x) { return x & 1; });
	output(o, o.begin(), copy_if.out); // 範囲:[0, 3) 値:1 3 5 <- 奇数のみ

	// 範囲の先頭の指定数の要素をコピーします。(C++20)
	auto copy_n = std::ranges::copy_n(r.begin(), 3, o.begin());
	output(o, o.begin(), copy_n.out); // 範囲:[0, 3) 値:1 2 3 <- 先頭3個

	return 0;
}

移動

ムーブセマンティクスによって要素が移動されます。移動元の範囲から要素は削除されませんが、要素の中身は未定義になります。下記の例では int 値は元の値が残り、string 値は空になっています。
ほかの動作はコピーと同じです。

move

範囲を移動します。

move_backward

範囲を後ろから移動します。

前方の一部が重なる範囲に移動する場合、move 関数だと重なる範囲が正しく移動できません。この場合、move_backword 関数で後ろの要素から移動すると回避できます。
逆に後方の一部が重なる範囲に移動する場合は move 関数を使用します。

移動のサンプル (C++11 iterator)

#include <algorithm>
#include <iostream>
#include <vector>

int main(int argc, char* argv[]) {
	std::vector<std::string> r = { "aaa", "bbb", "ccc", "ddd", "eee" };
	std::vector<std::string> o(r.size());

	// 範囲を移動します。(C++11)
	auto move = std::move(r.begin(), r.end(), o.begin());
	output(r, r.begin(), r.end()); // 範囲:[0, 5) 値: <- 移動元
	output(o, o.begin(), move); // 範囲:[0, 5) 値:aaa bbb ccc ddd eee <- 移動先

	// 範囲を後ろから移動します。(C++11)
	r = { "aaa", "bbb", "ccc", "ddd", "eee" };
	auto move_backward = std::move_backward(r.begin(), r.begin() + 3, r.end());
	output(r, r.begin(), r.end()); // 範囲:[0, 5) 値:  aaa bbb ccc <- 全体
	output(r, move_backward, r.end()); // 範囲:[2, 5) 値:aaa bbb ccc <- 移動先

	return 0;
}

移動のサンプル (C++20 range, iterator)

#include <algorithm>
#include <iostream>
#include <vector>

int main(int argc, char* argv[]) {
	std::vector<std::string> r = { "aaa", "bbb", "ccc", "ddd", "eee" };
	std::vector<std::string> o(r.size());

	// 範囲を移動します。(C++20)
	auto move = std::ranges::move(r, o.begin());
	output(r, r.begin(), r.end()); // 範囲:[0, 5) 値: <- 移動元
	output(o, o.begin(), move.out); // 範囲:[0, 5) 値:aaa bbb ccc ddd eee <- 移動先

	// 範囲を後ろから移動します。(C++20)
	r = { "aaa", "bbb", "ccc", "ddd", "eee" };
	auto move_backward = std::ranges::move_backward(r.begin(), r.begin() + 3, r.end());
	output(r, r.begin(), r.end()); // 範囲:[0, 5) 値:  aaa bbb ccc <- 全体
	output(r, move_backward.out, r.end()); // 範囲:[2, 5) 値:aaa bbb ccc <- 移動先

	return 0;
}

出力先のイテレーター

出力先のイテレーターとして以下のものが指定できます。
・インクリメント可能なイテレーター
r.begin() やポインタなどです。指定位置以降に要素が上書きされます。容量をあらかじめ確保しておく必要があります。
・back_insert_iterator
範囲の末尾に要素が追加されます。
・insert_iterator
指定位置以降に要素が挿入されます。
・ostream_iterator
コンソールやファイルなどに出力します。

#include <algorithm>
#include <iostream>
#include <list>
#include <vector>

int main(int argc, char* argv[]) {
	std::vector<int> r = { 1, 2, 3, 4, 5 };

	// インクリメント可能なイテレーター
	std::vector<int> o(r.size());
	auto copy = std::ranges::copy(r, o.begin());
	output(o, o.begin(), copy.out); // 範囲:[0, 5) 値:1 2 3 4 5

	// back_insert_iterator
	std::list<int> o_back_insert = { 0, 0, 0, 0, 0 };
	std::ranges::copy(r, std::back_inserter(o_back_insert));
	output(o_back_insert, o_back_insert.begin(), o_back_insert.end()); // 範囲:[0, 10) 値:0 0 0 0 0 1 2 3 4 5

	// insert_iterator
	std::list<int> o_insert = { 0, 0, 0, 0, 0 };
	std::ranges::copy(r, std::inserter(o_insert, std::next(o_insert.begin(), 3)));
	output(o_insert, o_insert.begin(), o_insert.end()); // 範囲:[0, 10) 値:0 0 0 1 2 3 4 5 0 0

	// ostream_iterator
	std::ranges::copy(r, std::ostream_iterator<int>(std::cout, ","));
	// 1,2,3,4,5,

	return 0;
}

コピーと移動のサンプル

copy の場合はコピーコンストラクタが、move の場合はムーブコンストラクタが呼び出されるのが分かります。

#include <algorithm>
#include <iostream>
#include <list>
#include <vector>

class S1 {
public:
	int i;
	std::string str;
public:
	S1(int _i, std::string _str) : i(_i), str(_str) {
		std::cout << "constructor(" << i << "," << str << ") ";
	}
	~S1() {
		std::cout << "destructor(" << i << "," << str << ") ";
	}
	S1(S1 const& s1) {
		std::cout << "copy-constructor(" << s1.i << "," << s1.str << ") ";
		*this = s1;
	}
	S1& operator=(S1 const& s1) {
		std::cout << "copy-operator=(" << s1.i << "," << s1.str << ") ";
		this->i = s1.i;
		this->str = s1.str;
		return *this;
	}
	S1(S1&& s1) noexcept {
		std::cout << "move-constructor(" << s1.i << "," << s1.str << ") ";
		*this = std::move(s1);
	}
	S1& operator=(S1&& s1) noexcept {
		std::cout << "move-operator=(" << s1.i << "," << s1.str << ") ";
		this->i = s1.i;
		this->str = std::move(s1.str);
		return *this;
	}
};

int main(int argc, char* argv[]) {
	S1 r[] = { { 1, "aaa" }, { 2, "bbb" } };
	std::cout << std::endl;
	// constructor(1,aaa) constructor(2,bbb)

	std::list<S1> o_copy;
	std::ranges::copy(r, std::back_inserter(o_copy));
	std::cout << std::endl;
	// copy-constructor(1,aaa) copy-operator=(1,aaa) copy-constructor(2,bbb) copy-operator=(2,bbb)
	for (auto& s1 : o_copy) {
		std::cout << s1.i << "," << s1.str << " ";
	}
	std::cout << std::endl;
	// 1,aaa 2,bbb <- コピー先

	std::list<S1> o_move;
	std::ranges::move(r, std::back_inserter(o_move));
	std::cout << std::endl;
	// move-constructor(1,aaa) move-operator=(1,aaa) move-constructor(2,bbb) move-operator=(2,bbb)
	for (auto& s1 : o_move) {
		std::cout << s1.i << "," << s1.str << " ";
	}
	std::cout << std::endl;
	// 1,aaa 2,bbb <- 移動先
	for (auto& s1 : r) {
		std::cout << s1.i << "," << s1.str << " ";
	}
	std::cout << std::endl;
	// 1, 2, <- 移動元

	return 0;
	// destructor(1,aaa) destructor(2,bbb) destructor(1,aaa) destructor(2,bbb) destructor(2,) destructor(1,)
}

コメント

タイトルとURLをコピーしました