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

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

新規登録して質問してみよう
ただいま回答率
85.46%
Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

C++

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

Q&A

解決済

3回答

1031閲覧

C++ 演算子オーバーロードの operator に対する & の要否を知りたい

saku26

総合スコア7

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

C++

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

0グッド

0クリップ

投稿2021/06/05 08:20

前提・実現したいこと

C++の初学者です。
クラスで演算子オーバーロードを試しています。
operator ++ の前に & をつける必要があるのかないのか、よくわかりません。
付けてもつけなくても結果が同じなのですが、参考書では&をつけています。
一方で、operator +については、参考書では&をつけていません。
&の要不要、必要ならばその意味について教えていただければ幸いです。

該当のソースコード

#include <iostream>

struct IntLike { int data; };
IntLike & operator ++(IntLike & obj)
{
++obj.data;
return obj;
}

int main()
{
IntLike a{ 1 };
++a;
std::cout << a.data;
return 0;
}

補足情報(FW/ツールのバージョンなど)

Visual Studio 2017を使用しています。

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

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

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

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

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

BeatStar

2021/06/05 08:48

多分ですが、『参照型』を理解すればわかる気がします。
saku26

2021/06/07 00:20

ありがとうございます。
guest

回答3

0

C++

1IntLike & operator ++(IntLike & obj) 2{ 3++obj.data; 4return obj; 5}

& をつけると obj そのものが返りますが、
つけないと obj のコピーが返ります。

なので

C++

1#include <iostream> 2 3struct IntLike { int data; }; 4 5IntLike operator ++(IntLike & obj) { // &つけない 6 ++obj.data; 7 return obj; 8} 9 10int main() { 11 IntLike a{ 1 }; 12 (++a).data = 123; 13 std::cout << a.data << std::endl; 14 15 return 0; 16}

これだと a.data は 2 となります。

投稿2021/06/05 08:50

episteme

総合スコア16614

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

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

saku26

2021/06/05 11:25

回答ありがとうございます。 以下のように、 operatorの前に&をつけるIntLike operatorの前に&をつけないIntLike2 operatorの前に&をつけず、objの前にも&をつけないIntLike3 を作ってテストしたのですが、 a.data=b.data=2 c.data=1 になります。 objの前の&の有無によってc.dataの実態は2つ存在することになるが、 operatorの前の&の有無は、a.dataもb.dataも実態は1つで同じように思ってしまうのですが、間違いでしょうか? #include <iostream> struct IntLike { int data; }; IntLike & operator ++(IntLike & obj) { ++obj.data; return obj; } struct IntLike2 { int data; }; IntLike2 operator ++(IntLike2 & obj) { ++obj.data; return obj; } struct IntLike3 { int data; }; IntLike3 operator ++(IntLike3 obj) { ++obj.data; return obj; } int main() { IntLike a{ 1 }; IntLike2 b{ 1 }; IntLike3 c{ 1 }; ++a; ++b; ++c; std::cout << a.data << "\n"; std::cout << b.data << "\n"; std::cout << c.data; return 0; }
episteme

2021/06/05 12:53

そのものを返すか/コピーを返すか の違いです。 上記コードは(返り値を使ってないので)同じ挙動に見えます。
saku26

2021/06/07 00:31 編集

ありがとうございます。 a.dataやb.dataは、returnのobjではなく、 引数の&objによる参照により、a.data=b.data=++obj.dataになっている、ということでしょうか? mainの方で、IntLike2のreturnによる返り値の値を使って確認してみたいのですが、どのようにすればよいでしょう? たびたび申し訳ございませんが、ご教示いただけると幸いです。
episteme

2021/06/07 00:30

回答で示してるやん。
saku26

2021/06/08 14:29 編集

もうしわけありませんが、回答の意図を正確に理解できていないようです。 まず、回答のコードは(++a).data = 123;の行でコンパイルエラーになりました。(++a).dataはlvalueではない為、=で代入できないということでしょうか。 Error C2106 '=': left operand must be l-value おっしゃられている意味は以下の通りと解釈したのですが、上記コンパイルエラーが発生する為、何か理解が不足しているのかもしれません。 2021/06/05 20:25のコードのクラス定義の場合、 (++b).dataとした場合、 (++b)はIntLikeのメンバー関数++の返り値で、(++a).dataはそのメンバー値であり、引数1に対して++して引数の値が変化した後の値をコピーした値 ++a.dataとした場合、 IntLike a{ 1 };の引数1に対して++して引数の値が変化した後の値そのもの (++a).data と ++a.dataの2つはメモリ上同じもの (++b).data と ++b.data の2つはメモリ上異なるもの という理解であってますでしょうか?
guest

0

& は参照を意味します。 質問の例の場合には operator++ の返却値は引数として渡された obj と同一の存在であるということです。 & を付けなかった場合には値が同じ新しいオブジェクトが新しく作られて返却されるということになります。

あなたがどうしたいかによって使い分ける必要があるので、まずは参照がどういうものであるのかについて学んでください。

ただし、習慣的には整数型の挙動と一致させるべきであると考えられているため、前置インクリメントの返却値には & を付け (参照を返し) て後置インクリメントの場合には & を付けない (新しくオブジェクトを生成して返す) のが普通です。

投稿2021/06/05 08:49

SaitoAtsushi

総合スコア5466

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

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

saku26

2021/06/05 11:30

回答ありがとうございます。 IntLike & operator ++(IntLike & obj)のobjの前の&の有無によって、IntLikeのインスタンスaの実態を参照に指定している(戻り値もreturn objですし)と理解していたのですが、operatorの前の&によって、引数のobjと戻り値のobjの実態が参照関係にあることを指定しているのでしょうか?
SaitoAtsushi

2021/06/05 12:06

・ 仮引数と実引数 ・ 返却値と return に与えた式 (の評価結果) の関係は変数の初期化の仕組みとほぼ対応付いていると考えるとわかりやすいかと思います。 つまり質問の例だと operator++ の引数は IntLike& obj=a; としたようなもので、返却値は IntLike& foo=obj; としたようなものです。 参照を参照する形で operator++ の返却値は与えられた引数の参照になっています。
saku26

2021/06/07 00:30

ありがとうございます。 mainの方で、IntLike2のreturnによる返り値の値を使って確認してみたいのですが、どのようにすればよいでしょう? たびたび申し訳ございませんが、ご教示いただけると幸いです。
guest

0

ベストアンサー

operator ++i の前に & をつける必要があるのかないのか、よくわかりません。
付けてもつけなくても結果が同じなのですが、参考書では&をつけています。

operator++ の前に & をつけるのは関数の返却値を左辺値にするためです。
返却値の使い方によっては結果が異なります。

質問のコードでは、++a; という形で operator++ を呼び出しているので、
返却値を使っていません。

& を付けて返却値が左辺値の場合、
IntLint a{1}, b{3}; ++a = b; (++a).data = 5; のように
返却値を変数として、IntLike全体や、メンバの data を変更できます。

& を付けなくて返却値が右辺値の場合、
cout << ++a.data; や b = ++a; のように返却値の値を参照することできますが
値を変更することができません。

さらに、回答に対するコメントを見ると、
operator++ の後の引数に & を付ける場合と付けない場合について混同しています。
これは別の問題です。

operator++ の前に & を付けているのは、本来の前置演算子 ++ が左辺値を
返すからそれに従っただけでしょう。int i = 3; ++i = 5; と書けます。

operator+ の前に & を付けないのは、本来の加法演算子+ が右辺値を返すから
それに従っただけでしょう。
int i = 3, j = 5; (i + j) = 7; とは書けません。

投稿2021/06/08 16:33

編集2021/06/09 03:16
kazuma-s

総合スコア8224

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

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

saku26

2021/06/09 02:42

ご回答ありがとうございます。 1つ確認させてください。 「& を付けて」返却値が右辺値の場合、 とありますが、これは正しくは「&を付けずに」でしょうか? といいますのは、「operator++ の前に & をつけるのは関数の返却値を左辺値にするため」とのことですので、矛盾しているように思った為です。
kazuma-s

2021/06/09 03:15

すみません。間違いです。訂正します。 それで、この説明で納得できますか? まだ疑問点がありますか?
saku26

2021/06/09 04:15

これで納得いったように思います。 他の方の回答で、以下の例を示されていたのですが、(++a).data = 123;の行でコンパイルエラーになりました。これはoperator++の前に&を付けていないので、左辺値になっていないから、ということですね。 #include <iostream> struct IntLike { int data; }; IntLike operator ++(IntLike & obj) { // &つけない ++obj.data; return obj; } int main() { IntLike a{ 1 }; (++a).data = 123; std::cout << a.data << std::endl; return 0; } 他の方の回答では、 「& をつけると obj そのものが返りますが、つけないと obj のコピーが返ります。」 というご説明でしたが、これは間違いなのでしょうか?それとも、本質的にはkazuma-s様の回答と同じことなのでしょうか?
qt6hy

2021/06/09 05:19 編集

>「& をつけると obj そのものが返りますが、つけないと obj のコピーが返ります。」 >というご説明でしたが、これは間違いなのでしょうか? 間違っていません。正しいです。 ・左辺値にするため と ・&をつけると参照扱いになる(つけないとコピーになる) は分けて考えたほうがわかりやすいかも。 なので純粋に「&つけると参照渡しになり、つけないとコピー渡し(値渡し)になる」ということを理解してください。 ///////////////////////////////////////////////////////////// void doSomething1(int a) { // ここでの a のアドレスは、x とは異なる(a はコピーだから) a = 1; // コピーの値を変更しているだけ return; } void doSomething2(int& a) { a = 2; // 参照の値を変えているので、x が変わる return; } int main() { int x = 0; // x==0 doSomething1(x); // x==0 doSomething2(x); // x==2 }
kazuma-s

2021/06/09 05:22

VC++ では、なぜかエラーになりません。 C++ の規格書を詳しく調べてみる必要がありそうです。 引数の obj は「main の a への参照」です。 & をつけると「main の a への参照」自身が返るので、 a のメンバ data を 123に変更できます。 & を付けないと、一時的な返却値オブジェクトに a の値をコピーします。 返却値オブジェクトは左辺値ではありませんが、一時オブジェクトなので、 消えるまではどこかに存在するわけで、そのメンバ dataの変更を VC++ は 許しているのかもしれません。
saku26

2021/06/09 05:57

疑問点は解消した((++a).data = 123; が、kazuma-s様の方でコンパイルできちゃう点を除いて)と思います。 ありがとうございました。
Bearded-Ockham

2021/06/09 13:48

VC++でエラーにならないのは、デフォルトで permissive オプションが有効だからです。/permissive- のオプションを渡して無効にすればエラーになります。
saku26

2021/06/12 11:57

コメントありがとうございます。 Visual Studio 2017 Version 15.5.4を使用しており、デフォルトで/permissive-になっておりました。その状態ではコンパイルエラーとなり、これを外す(Conformance modeをNoにする)とコンパイル可能となりました。 こちらの解説によれば、「標準に準拠したコンパイラの動作を指定できます。 このオプションは、制限のない動作を無効にし、 /Zc 厳密な準拠のコンパイラオプションを設定します。」とのことですので、/permissive-をつけるとコンパイルできないということは、(++a).dataは/Zcに準拠していないと理解しました。 https://docs.microsoft.com/ja-jp/cpp/build/reference/permissive-standards-conformance?view=msvc-160
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.46%

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

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

質問する

関連した質問