質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

1回答

1272閲覧

std::forwardの戻り値を格納する変数の定義のしかたで結果が変わってしまう

退会済みユーザー

退会済みユーザー

総合スコア0

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2020/07/05 06:19

編集2020/07/05 06:28

前提

ムーブコンストラクタが動いた時標準出力に[ムーブ]と出力
コピーコンストラクタが動いた時標準出力に[コピー]と出力
するような構造体Hogeと

ユニバーサル参照とstd::forwardを使い
オーバーロードなしの同じ1つの関数で
右辺値が代入されたら引数から内部の変数へムーブし
左辺値が代入されたら引数から内部の変数へコピー
するような関数funcを作っていた時理解できない問題が発生したので
質問させていただきます。

ちなみに関数funcはenable_ifを使うことで
構造体Hoge以外の型を受け付けないようになってます。

下記のコードはC++17でコンパイルしています。

質問内容

質問は2つございます。

質問1:
関数func中の

c++

1Hoge hoge; 2hoge = std::forward<Type>(arg);

の部分を

c++

1Type hoge = std::forward<Type>(arg);

と書いてしまうと下記の結果
(#define PROBLEM_OCCURRED_1を有効にした場合の実行結果)のようになります。
なぜ左辺値代入時コピーが行われず右辺値代入時もコピーが行われてしまうのでしょうか。

質問2:
また質問①のようにfunc内で

c++

1Hoge hoge = std::forward<Type>(arg);

と書いてしまうと
(#define PROBLEM_OCCURRED_2を有効にした場合の実行結果)のようになります
なぜ右辺値代入時ムーブが行われずコピーがが行われてしまうのでしょうか。

ご回答どうかよろしくお願いします。

備考

上記の問題を発生させたいのならば下記のコード中
4行目のPROBLEM_OCCURRED_1(1つ目の問題を発生させる)か
5行目のPROBLEM_OCCURRED_2(2つ目の問題を発生させる)の
コメントアウトを外してもらえば発生させられます。

問題のコード

c++

1#include <iostream> 2#include <type_traits> 3 4//#define PROBLEM_OCCURRED_1//質問①の問題発生させるスイッチ 5//#define PROBLEM_OCCURRED_2//質問②の問題発生させるスイッチ 6 7// 8// 構造体Hogeのプロトタイプ宣言 9// 10struct Hoge; 11 12// 13// Hoge判定用メタ関数 14// 15template<class Type> struct is_hoge:std::false_type{}; 16template<> struct is_hoge<Hoge&>:std::true_type{}; 17template<> struct is_hoge<const Hoge&>:std::true_type{}; 18template<> struct is_hoge<Hoge&&>:std::true_type{}; 19template<> struct is_hoge<Hoge>:std::true_type{}; 20 21// 22// 構造体Hogeの定義 23// 24struct Hoge{ 25 Hoge()noexcept = default; 26 ~Hoge() = default; 27 Hoge& operator=(const Hoge& hoge)noexcept{std::cout << "コピー" << std::endl; return *this;} 28 Hoge& operator=(Hoge&& hoge)noexcept{std::cout << "ムーブ" << std::endl; return *this;} 29 Hoge(const Hoge& hoge)noexcept{*this = hoge;} 30 Hoge(Hoge&& hoge)noexcept{*this = hoge;} 31}; 32 33// 34// 内部で確保したHogeのオブジェクトへコピーorムーブする関数 35// 引数はユニバーサル参照となっており右辺値でも左辺値でも入力可能 36// enable_ifを使うことでHoge以外代入できないようになっている 37// 38template<class Type> 39typename std::enable_if<is_hoge<Type>::value>::type func(Type&& arg)noexcept{ 40#if defined(PROBLEM_OCCURRED_1) 41 Type hoge = std::forward<Type>(arg);//質問①:エラーは起こらないが左辺値代入時コピーが行われず右辺値代入時もコピーが行われてしまう 42 43#elif defined(PROBLEM_OCCURRED_2) 44 Hoge hoge = std::forward<Type>(arg);//質問②:エラーは起こらないが右辺値代入時ムーブが行われずコピーがが行われる 45 46#else 47 Hoge hoge; 48 hoge = std::forward<Type>(arg);//引数が左辺値ならコピー,右辺値ならムーブになる 49 50#endif 51 52} 53 54// 55// メイン関数 56// 57int main(void) 58{ 59 std::cout << "/////////////////////////////////////////////" << std::endl; 60 std::cout << "左辺値代入" << std::endl; 61 Hoge lr; 62 func(lr); 63 64 std::cout << "/////////////////////////////////////////////" << std::endl; 65 std::cout << "右辺値代入" << std::endl; 66 func(Hoge()); 67 68 return 0; 69} 70

問題を発生させなかった場合の実行結果

terminal

1///////////////////////////////////////////// 2左辺値代入 3コピー 4///////////////////////////////////////////// 5右辺値代入 6ムーブ

ちゃんと左辺値代入でも右辺値代入正しい挙動をしています。

#define PROBLEM_OCCURRED_1を有効にした場合の実行結果

terminal

1///////////////////////////////////////////// 2左辺値代入 3///////////////////////////////////////////// 4右辺値代入 5コピー

左辺値代入時コピーが行われず右辺値代入時もコピーが行われてしまっている。

#define PROBLEM_OCCURRED_2を有効にした場合の実行結果

terminal

1///////////////////////////////////////////// 2左辺値代入 3コピー 4///////////////////////////////////////////// 5右辺値代入 6コピー

右辺値代入時ムーブが行われずコピーがが行われてしまっている。

開発環境の備考

ツールの種類ツールの名前バージョン
コンパイラclang++6.0.0
OSLinux Mint18.3

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

クラスHogeのムーブコンストラクタ定義に問題があるようです。質問文にある実装(★箇所)では、ムーブコンストラクタ内でコピー代入演算子が呼び出されます。

c++

1struct Hoge{ 2 Hoge()noexcept = default; 3 ~Hoge() = default; 4 Hoge& operator=(const Hoge& hoge)noexcept{std::cout << "コピー" << std::endl; return *this;} 5 Hoge& operator=(Hoge&& hoge)noexcept{std::cout << "ムーブ" << std::endl; return *this;} 6 Hoge(const Hoge& hoge)noexcept{*this = hoge;} 7 Hoge(Hoge&& hoge)noexcept{*this = hoge;} // ★ 8};

正しくは、次のように実装すべきです。

c++

1struct Hoge{ 2 // (省略) 3 Hoge(Hoge&& hoge)noexcept{*this = std::move(hoge);} 4};

投稿2020/07/05 07:16

yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/07/05 07:35

yohhoy様 右辺値≠右辺値参照と言うことですね… うっかりしてました。 的確なご回答ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問