MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
C++の基礎 - 完全転送のソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
C++の基礎 - 完全転送
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == 完全転送 (perfect forwarding) はC++11で導入された機能であり、関数の引数を他の関数に転送する時に、引数の値カテゴリ (左辺値・右辺値) と 型修飾 (const等) を保持する技術である。<br> ユニバーサル参照とstd::forwardを組み合わせることで実現される。<br> <br> 完全転送は、C++の高度な機能であり、以下に示す事柄を理解する必要がある。<br> * ユニバーサル参照 と <code>std::forward</code> をセットで使用する。 * 引数の値カテゴリと型修飾を保持したまま転送できる。 * <code>std::move</code> は無条件に右辺値にキャストするが、<code>std::forward</code> は条件付き。 * 可変長引数テンプレートと組み合わせることにより、柔軟性が向上する。 * 標準ライブラリの多くの関数で使用されている。 <br> 完全転送を適切に使用することで、効率的で保守性の高いコードを書くことができる。<br> <br><br> == 完全転送の必要性 == ==== 背景 ==== 関数の引数を別の関数に転送する時、従来の方法では値カテゴリの情報が失われる問題があった。<br> この問題を解決するために、完全転送が必要となる。<br> <br> <syntaxhighlight lang="c++"> #include <iostream> // 左辺値参照 void target(int& x) { std::cout << "Lvalue reference version" << std::endl; } // 右辺値参照 void target(int&& x) { std::cout << "Rvalue reference version" << std::endl; } // 不完全な転送の例 template<typename T> void forwardBad(T&& param) { // paramは名前を持つため、常に左辺値として扱われる target(param); // 常に左辺値参照が呼ばれる } int main() { int x = 10; forwardBad(x); // 左辺値を渡す -> 左辺値参照が呼ばれる forwardBad(20); // 右辺値を渡す -> 左辺値参照が呼ばれる (問題) forwardBad(std::move(x)); // 右辺値を渡す -> 左辺値参照が呼ばれる (問題) return 0; } </syntaxhighlight> <br><br> == std::forwardの役割 == ==== 基本的な動作 ==== std::forwardは、引数の値カテゴリを保持したまま転送するためのキャスト関数である。<br> * 左辺値として渡された引数は、左辺値として転送される。 * 右辺値として渡された引数は、右辺値として転送される。 <br> ==== std::forwardの使用例 ==== <syntaxhighlight lang="c++"> #include <iostream> #include <utility> // 左辺値参照 void target(int& x) { std::cout << "Lvalue reference version: " << x << std::endl; } // 右辺値参照 void target(int&& x) { std::cout << "Rvalue reference version: " << x << std::endl; } // 完全転送の実装 template<typename T> void forwardGood(T&& param) { // std::forwardを使用して値カテゴリを保持 target(std::forward<T>(param)); } int main() { int x = 10; forwardGood(x); // 左辺値を渡す -> 左辺値参照が呼ばれる forwardGood(20); // 右辺値を渡す -> 右辺値参照が呼ばれる forwardGood(std::move(x)); // 右辺値を渡す -> 右辺値参照が呼ばれる return 0; } </syntaxhighlight> <br><br> == std::forwardの仕組み == ==== 内部定義の概念 ==== std::forwardは、参照の折り畳み規則と型推論を利用している。<br> <br> <syntaxhighlight lang="c++"> // std::forwardの簡略化された定義のイメージ // 左辺値参照 template<typename T> T&& forward(typename std::remove_reference<T>::type& param) { return static_cast<T&&>(param); } // 右辺値参照 template<typename T> T&& forward(typename std::remove_reference<T>::type&& param) { return static_cast<T&&>(param); } </syntaxhighlight> <br> ==== 型推論との組み合わせ ==== <syntaxhighlight lang="c++"> template<typename T> void wrapper(T&& param) { // ケース1 : 左辺値を渡した場合 // T = int& // std::forward<int&>(param) → static_cast<int& &&>(param) // 参照の折り畳み: int& && → int& // 結果: 左辺値参照 // ケース2: 右辺値を渡した場合 // T = int // std::forward<int>(param) → static_cast<int&&>(param) // 結果: 右辺値参照 target(std::forward<T>(param)); } </syntaxhighlight> <br><br> == 完全転送の例 == ==== ファクトリ関数の実装 ==== <syntaxhighlight lang="c++"> #include <iostream> #include <memory> #include <string> #include <utility> class Product { private: std::string name; int id; double price; public: Product(const std::string& n, int i, double p) : name(n), id(i), price(p) { std::cout << "Product created (lvalue string): " << name << std::endl; } Product(std::string&& n, int i, double p) : name(std::move(n)), id(i), price(p) { std::cout << "Product created (rvalue string): " << name << std::endl; } void display() const { std::cout << "Product: " << name << ", ID: " << id << ", Price: " << price << std::endl; } }; // 完全転送を使用したファクトリ関数 template<typename... Args> std::unique_ptr<Product> createProduct(Args&&... args) { return std::make_unique<Product>(std::forward<Args>(args)...); } int main() { std::string name1 = "Widget"; // 左辺値を転送 auto p1 = createProduct(name1, 100, 29.99); // 右辺値を転送 auto p2 = createProduct(std::string("Gadget"), 200, 49.99); // 混在 auto p3 = createProduct("Tool", 300, 19.99); p1->display(); p2->display(); p3->display(); return 0; } </syntaxhighlight> <br> ==== ラッパークラスの実装 ==== <syntaxhighlight lang="c++"> #include <iostream> #include <utility> #include <vector> // 要素を保持するコンテナ template<typename T> class Container { private: std::vector<T> data; public: // 完全転送を使用した要素追加 template<typename U> void add(U&& item) { std::cout << "Adding item..." << std::endl; data.push_back(std::forward<U>(item)); } // 完全転送を使用した構築 template<typename... Args> void emplace(Args&&... args) { std::cout << "Emplacing item..." << std::endl; data.emplace_back(std::forward<Args>(args)...); } void display() const { for (const auto& item : data) { std::cout << item << " "; } std::cout << std::endl; } }; int main() { Container<std::string> container; std::string str = "Hello"; container.add(str); // 左辺値 : コピー container.add(std::string("World")); // 右辺値 : ムーブ container.add(std::move(str)); // ムーブ container.emplace(5, 'X'); // 直接構築 container.display(); return 0; } </syntaxhighlight> <br> ==== コールバック関数の転送 ==== <syntaxhighlight lang="c++"> #include <iostream> #include <functional> #include <utility> // コールバックを実行する関数 template<typename Func, typename... Args> auto executeCallback(Func&& func, Args&&... args) -> decltype(std::forward<Func>(func)(std::forward<Args>(args)...)) { std::cout << "Executing callback..." << std::endl; return std::forward<Func>(func)(std::forward<Args>(args)...); } // テスト用の関数 int add(int a, int b) { return a + b; } void printMessage(const std::string& msg) { std::cout << "Message: " << msg << std::endl; } int main() { // 関数ポインタの転送 int result1 = executeCallback(add, 10, 20); std::cout << "Result: " << result1 << std::endl; // ラムダ式の転送 auto multiply = [](int a, int b) { return a * b; }; int result2 = executeCallback(multiply, 5, 6); std::cout << "Result: " << result2 << std::endl; // std::functionの転送 std::function<void(const std::string&)> func = printMessage; executeCallback(func, "Hello, World!"); // 一時的なラムダ式(右辺値)の転送 executeCallback([](int x) { std::cout << "Value: " << x << std::endl; }, 42); return 0; } </syntaxhighlight> <br><br> == 可変長引数テンプレートとの組み合わせ == ==== 基本パターン ==== 完全転送は、可変長引数テンプレートと組み合わせることが多い。<br> <syntaxhighlight lang="c++"> #include <iostream> #include <utility> // 再帰の終端 void print() { std::cout << std::endl; } // 可変長引数と完全転送 template<typename T, typename... Args> void print(T&& first, Args&&... rest) { std::cout << std::forward<T>(first) << " "; print(std::forward<Args>(rest)...); } int main() { print(1, 2.5, "Hello", 'A'); std::string str = "World"; print(str, 42, std::string("Test")); return 0; } </syntaxhighlight> <br> ==== タプルとの組み合わせ ==== <syntaxhighlight lang="c++"> #include <iostream> #include <tuple> #include <utility> #include <string> // タプルを構築する関数 template<typename... Args> auto makeTupleForward(Args&&... args) { return std::make_tuple(std::forward<Args>(args)...); } int main() { std::string str = "Hello"; int value = 42; // 完全転送を使用してタプルを構築 auto t1 = makeTupleForward(str, value, 3.14); // コピー auto t2 = makeTupleForward(std::move(str), value, 3.14); // ムーブ std::cout << std::get<0>(t1) << ", " << std::get<1>(t1) << ", " << std::get<2>(t1) << std::endl; std::cout << std::get<0>(t2) << ", " << std::get<1>(t2) << ", " << std::get<2>(t2) << std::endl; return 0; } </syntaxhighlight> <br><br> == std::move と std::forwardの違い == ==== 使い分け ==== std::move と std::forwardは似ているが、使用目的が異なる。<br> <br> <center> {| class="wikitable" style="background-color:#fefefe;" |- ! 特性 !! std::move !! std::forward |- | 目的 || 無条件に右辺値にキャスト || 条件付きで右辺値にキャスト |- | 使用場面 || 左辺値を右辺値として扱いたい || 引数の値カテゴリを保持したい |- | テンプレート || 不要 || 必須(テンプレートパラメータを指定) |- | 型推論 || 自動 || テンプレート引数に依存 |- | 典型的な使用 || ムーブ操作 || 完全転送 |} </center> <br> ==== 比較例 ==== <syntaxhighlight lang="c++"> #include <iostream> #include <utility> #include <string> void process(std::string& str) { std::cout << "Lvalue: " << str << std::endl; } void process(std::string&& str) { std::cout << "Rvalue: " << str << std::endl; } template<typename T> void useMove(T&& param) { // 常に右辺値として転送 process(std::move(param)); } template<typename T> void useForward(T&& param) { // 値カテゴリを保持して転送 process(std::forward<T>(param)); } int main() { std::string lvalue = "Left"; std::cout << "--- Using std::move ---" << std::endl; useMove(lvalue); // 右辺値として転送 useMove(std::string("Right")); // 右辺値として転送 std::cout << "\n--- Using std::forward ---" << std::endl; useForward(lvalue); // 左辺値として転送 useForward(std::string("Right")); // 右辺値として転送 return 0; } </syntaxhighlight> <br><br> == 完全転送の注意 == ==== テンプレートパラメータの指定忘れ ==== <syntaxhighlight lang="c++"> template<typename T> void func(T&& param) { // 誤り : テンプレートパラメータを指定していない // target(std::forward(param)); // コンパイルエラー // 正しい : テンプレートパラメータを指定 target(std::forward<T>(param)); // OK } </syntaxhighlight> <br> ==== 複数回の転送 ==== <u>std::forwardは、1度しか使用できない。</u><br> <u>2回目以降は左辺値として扱われる。</u><br> <br> <syntaxhighlight lang="c++"> #include <iostream> #include <utility> void process(int&& x) { std::cout << "Rvalue: " << x << std::endl; } template<typename T> void doubleForward(T&& param) { process(std::forward<T>(param)); // OK : 右辺値として転送 // process(std::forward<T>(param)); // 危険 : 2回目の転送 } int main() { doubleForward(10); return 0; } </syntaxhighlight> <br> ==== 初期化子リストの転送 ==== 初期化子リストは完全転送できない。<br> <br> <syntaxhighlight lang="c++"> #include <vector> #include <utility> template<typename T, typename... Args> T create(Args&&... args) { return T(std::forward<Args>(args)...); } int main() { // エラー : 初期化子リストは推論できない // auto vec = create<std::vector<int>>({1, 2, 3, 4, 5}); // 回避策 : 明示的に初期化子リストを作成 auto vec = create<std::vector<int>>(std::initializer_list<int>{1, 2, 3, 4, 5}); return 0; } </syntaxhighlight> <br> ==== オーバーロード解決の問題 ==== <syntaxhighlight lang="c++"> #include <iostream> void func(int x) { std::cout << "int version" << std::endl; } void func(int& x) { std::cout << "int& version" << std::endl; } template<typename T> void wrapper(T&& param) { // オーバーロード解決が予期しない結果になる可能性 func(std::forward<T>(param)); } int main() { int x = 10; wrapper(x); // どちらが呼ばれるか? return 0; } </syntaxhighlight> <br><br> == 完全転送の推奨される使用方法 == ==== 推奨事項 ==== * 常に <code>std::forward</code> にテンプレートパラメータを明示的に指定する。 * ユニバーサル参照 と <code>std::forward</code> はセットで使用する。 * <u><code>std::forward</code>は、引数を1回だけ転送する。</u> * 可変長引数テンプレートと組み合わせて柔軟性を高める。 * <code>const</code> や <code>volatile</code> 修飾にも注意を払う。 <br> ==== テンプレート関数の設計パターン ==== <syntaxhighlight lang="c++"> #include <utility> // パターン1 : 単一引数の完全転送 template<typename T> void singleArgument(T&& arg) { target(std::forward<T>(arg)); } // パターン2 : 複数引数の完全転送 template<typename T1, typename T2> void multipleArguments(T1&& arg1, T2&& arg2) { target(std::forward<T1>(arg1), std::forward<T2>(arg2)); } // パターン3 : 可変長引数の完全転送 template<typename... Args> void variadicArguments(Args&&... args) { target(std::forward<Args>(args)...); } // パターン4 : 戻り値も転送 template<typename Func, typename... Args> auto callAndForward(Func&& func, Args&&... args) -> decltype(std::forward<Func>(func)(std::forward<Args>(args)...)) { return std::forward<Func>(func)(std::forward<Args>(args)...); } </syntaxhighlight> <br><br> == パフォーマンスへの影響 == 完全転送を使用することにより、以下に示すパフォーマンス向上が期待できる。<br> * 不要なコピーを完全に排除できる。 * ムーブセマンティクスを最大限活用できる。 * テンプレートコードのオーバーヘッドが最小化される。 * コンパイラの最適化が効きやすくなる。 <br> ==== パフォーマンス比較 ==== <syntaxhighlight lang="c++"> #include <iostream> #include <vector> #include <chrono> #include <utility> class LargeObject { private: std::vector<int> data; public: LargeObject(size_t size = 1000000) : data(size, 42) {} LargeObject(const LargeObject& other) : data(other.data) { std::cout << "Copy constructor" << std::endl; } LargeObject(LargeObject&& other) noexcept : data(std::move(other.data)) { std::cout << "Move constructor" << std::endl; } }; // 値渡し (コピーが発生) void processByValue(LargeObject obj) { // 何らかの処理 // ...略 } // 完全転送 template<typename T> void processByForward(T&& obj) { // std::forwardで転送 processByValue(std::forward<T>(obj)); } int main() { LargeObject obj1; std::cout << "--- Without perfect forwarding ---" << std::endl; processByValue(obj1); // コピーが発生 std::cout << "\n--- With perfect forwarding ---" << std::endl; processByForward(LargeObject()); // ムーブが発生 return 0; } </syntaxhighlight> <br><br> == 使用例 == ==== 標準ライブラリでの使用 ==== C++標準ライブラリの多くの関数で完全転送が使用されている。<br> * std::make_unique * std::make_shared * std::vector::emplace_back * std::map::emplace * std::thread::thread <br> ==== スマートポインタの構築 ==== <syntaxhighlight lang="c++"> #include <memory> #include <iostream> #include <string> class Resource { private: std::string name; int value; public: Resource(std::string n, int v) : name(std::move(n)), value(v) { std::cout << "Resource created: " << name << std::endl; } ~Resource() { std::cout << "Resource destroyed: " << name << std::endl; } }; int main() { std::string name = "Resource1"; // 完全転送を使用したスマートポインタの構築 auto ptr1 = std::make_unique<Resource>(name, 100); auto ptr2 = std::make_unique<Resource>(std::string("Resource2"), 200); auto ptr3 = std::make_shared<Resource>("Resource3", 300); return 0; } </syntaxhighlight> <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:C++]]
C++の基礎 - 完全転送
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse