最近Reactive Extensionsなどで、C++を使うことが増えたことで、&&(ユニバーサル参照)を見かけたことから、ムーブセマンティクスについて興味を持ったため調べてみました。
「ReactiveX / RxCpp」
https://decode.red/net/archives/1053
C++を最初に使ったのはかなり前(まだMicrosoftのコンパイラが対応する前)でしたが、今のC++は当時の面影がないほど記法が変化しており、最近も3年ごとにアップデートされていて、ちょっと目を離すと、使わない機能などは読解が困難になるほどです。難易度が高い言語ですが、機能が豊富で実戦(いろんな意味で)では強いというメリットもあることから、最近見直しています。その一環としてムーブセマンティクス、ムーブコンストラクタといわれるものを試してみました。
所有権の移動は、最近Blockchainのブログでも触れましたが、このジャンルで重要な考え方なので興味を持ちました。
「Move / Aptos」
http://bitlife.me/bc/2022/10/23/
C++の記法は、柔軟であるゆえ複雑になる傾向があるので、まずは参考サイトのコードを自分なりにアレンジして動かしてみました。
環境)
https://wandbox.org/
参考)
https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html
https://theolizer.com/cpp-school2/cpp-school2-13/#std_forward
https://marycore.jp/prog/cpp/move-constructor/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
#include <iostream> #include <string> struct DispHelper { DispHelper(char const* title, void const* ptr, std::string s) { std::cout << title << ptr << " " << s <<"\n"; } }; class Foo { DispHelper mDispHelper; std::string mString; public: // コンストラクタ1(const参照で受け取る) Foo(std::string const& iString) : mDispHelper("Copy: iString.data()=", iString.data(), iString.data()), mString(iString) { std::cout << "Copy: mString.data()="; std::cout .operator<< (mString.data()); std::cout << " " << mString.data() << "\n"; } // コンストラクタ2(右辺値参照で受け取る) Foo(std::string&& iString) : mDispHelper("Move: iString.data()=", iString.data(), iString.data()), mString(std::move(iString)) { std::cout << "Move: mString.data()="; std::cout .operator<< (mString.data()); std::cout << " " << mString.data() << "\n"; } }; int main() { int x = 123; int& lvalue = x; //(左辺値参照型) int&& rvalue = 456; //(右辺値参照型) std::cout << lvalue << "\n"; std::cout << rvalue << "\n"; std::string str1 = "Hello!"; std::string str2 = std::move(str1); std::cout << "1:"; std::cout .operator<< (str1.data()) << " " << str1 << "\n"; std::cout << "2:"; std::cout .operator<< (str2.data()) << " " << str2 << "\n"; Foo f0(std::string("abcdefghijklmnop")); std::string s1("ABCDEFGHIJKLMNOP"); Foo f1(s1); std::cout << "s1[" << s1.size() << "] = " << s1 << "\n"; Foo f2(std::move(s1)); std::cout << "s1[" << s1.size() << "] = " << s1 << "\n"; } |
所有権を移動しただけでは、アドレスが変わらない、というテストです。ただし16文字より少ないと変化します。これは参考サイトにもありますが、SSO(Small-string optimization)によるもので、性能劣化はしないとのことです。
main関数の最初の3行は、左辺値参照と右辺値参照は、このような書き方しかできないという例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <string> #include <cstdio> #include <utility> #include <stdio.h> struct A { int* p; // ムーブコンストラクタ A(A&& v) : p(v.p) { printf("move\n");v.p = nullptr; } // デフォルトコンストラクタ A() : p(new int(123)) {printf("default\n");} // コピーコンストラクタ A(const A& v) : p(new int(*v.p)) {printf("copy\n");} // デストラクタ ~A() { printf("delete\n");delete p; } }; int main() { A a(std::move(A())); printf("a: %d\n", *(a.p)); A b = a; printf("b: %d\n", *(b.p)); A c; printf("c: %d\n", *(c.p)); A d(std::move(c)); printf("d: %d\n", *(d.p)); if(c.p == nullptr){ printf("c: null\n"); } return 0; } |
cは、aと意味は同じですが、記法の確認のためと、移動したことの確認のため追加しました。
参考サイトでも使われていてはじめて知ったのですが、Wandbox、便利です。