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

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

ただいまの
回答率

88.80%

引数にコンストラクタのインスタンスを渡すことの賛否

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 2,327

s8079

score 33

質問内容

私は場合分けを減らすためにしばしば引数にコンストラクタのインスタンスを渡します.
引数にコンストラクタのインスタンスを渡している例や説明をあまりにも見かけないので心配になり質問しました.
エラーが出るわけではないですが,このような書き方はやめた方が良いのでしょうか.

該当のソースコード

#include <vector>
void func(std::vector<int> vec) {
    return;
}
int main() {
    func(std::vector<int>());
    return 0;
}

追記

以下の書き方はいずれも正しいのでしょうか.

    std::vector<int> vec1;
    // std::vector<int> vec2();
    std::vector<int> vec2{};
    std::vector<int> vec3 = std::vector<int>();
    std::vector<int> *pvec = new std::vector<int>();
    delete pvec;
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • LouiS0616

    2019/06/26 08:23

    func内でのvecの利用目的は何でしょうか。

    キャンセル

  • majiponi

    2019/06/26 09:44 編集

    追記内容の意図は?

    キャンセル

  • t_obara

    2019/06/26 12:41

    正しいの定義を明確にされた方がよろしいかと思います。
    エラーが出ない、意図と異なる動作をする、メンテナンス性を悪化させる
    など貴殿が考える「正しい」が何を意図しているのかを伝えると、より
    適切な回答が得られるのではないかと思います。

    キャンセル

回答 3

checkベストアンサー

+1

こんにちは。

C++では、

func(std::vector<int>());  // ①


std::vector<int> vec;
func(vec);                  // ②


では、同じように見えてかなり異なる意味付けがなされています。

前者は、式のように見えませんが実は式を渡しています。後者は変数を渡しています。
式と変数の大きな差は、ざっくり書くと(一部の例外を除き)結果が右辺値(値)か左辺値(容器)かの差です。
右辺値と左辺値の最大の差は、そこへ値を「代入」できるかどうかです。普通は右辺値に値を代入することはできませんが、左辺値は普通に値を代入できます。(a=a+123;において a は左辺値、a+123の計算結果は右辺値です。)

関数の仮引数はコピー渡しなら右辺値、左辺値共に受け取れます。ご提示されているfoo()関数はご存知のようにコピー渡しですから、上記の前者・後者どちらとも受け取れます。ただし、コピー渡しですから当然コピーが発生します。int型等の小さな変数を引き渡す時に好ましい渡し方です。

さて、大きな変数を渡す時はコピーのコストを嫌うため参照渡しが好まれます。この時に右辺値、左辺値が問題になります。
ノーマルな参照型の仮引数は左辺値しか受け取れません。
constな参照型の仮引数は左辺値、右辺値共に受け取れますが、constですので内容を変更できません。
右辺値参照型の仮引数は右辺値を受け取れ内容も変更できますが、左辺値を受け取れません。(右辺値は式の計算結果ですのでその内容を変更しても意味はないので内容を変更する目的で使われることはあまりなさそうです。)

void foo0(std::vector<int>& vec) { }       // ノーマルな参照渡し
void foo1(std::vector<int>const & vec) { } // constな参照渡し
void foo2(std::vector<int>&& vec) { }      // 右辺値参照渡し

整数型等の小さな変数を渡す時は、foo の形式がよく使われます。
大きな変数を渡すだけの時は foo1 の形式が良く思います。
渡された引数を加工した時は、foo0が良く使われます。

foo2は比較的最近(といっても2011年ですが)使えるようになった使い方です。
あまり頻繁には使いませんがリソースの所有権を移動したい際に便利です。身近な例ではstd::unique_ptrでしょう。

エラーが出るわけではないですが,このような書き方はやめた方が良いのでしょうか.

fooの形式ではコピーが発生しますので、std::vector等の巨大になりうる変数を渡す時にはあまり使われません。その意味で避けた方が好ましいと思います。

さて、ベターCとしてC++を使っている人は foo0 の形式を使う場合が多いと思います。この時①の渡し方ができません。
C++のちょっと深い部分を使い始めた方は foo1 の形式を使う場合が多いと思います。これは②の渡し方もできますが、どちらかというと「渡された変数の内容を変更しませんよ」という意味を込めて使うので②のケースを見かけることは少ないかもしれませんが、普通に使って問題ないです。
foo2 は歴史も浅いですし、最近出現したということは使う場面も多くはない(もし非常に多いならもっと初期に出現している筈)ので、あまり見かけなくても不思議ではありません。

追記

vec2は構文は合ってますが、意図とは合っていないと思います。
これはstd::vector<int>を返却する関数vec2()前方参照宣言と解釈されます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/06/26 20:15 編集

    コンストラクタにより生成された一時オブジェクトは右辺値だから,値渡しするかconst参照するのが一般的で,最近では右辺値参照という技術もあるというように理解しました.しかし,kazuma-s氏によるとコンストラクタにより生成された一時オブジェクトは右辺値ではなく,左辺値だそうです.私も手元で試してみたところ,特にエラーもなく,値を代入することができてしましました.

    キャンセル

  • 2019/06/26 21:15

    おおお、左辺値な一時変数とはびっくり。確かにgcc, clang共にエラーになりませんでした。
    でも、foo0(std::vector<int>());とすると、

    error: cannot bind non-const lvalue reference of type 'std::vector<int>&' to an rvalue of type 'std::vector<int>'
    15 | foo0(std::vector<int>());
    | ^~~~~~~~~~~~~

    というエラーになるので、この場合は右辺値です。

    試しに、 foo0(std::vector<int>()={1, 2, 3});とすると通りました。
    https://wandbox.org/permlink/ngpE1FOI5zvHs0gM

    恐らく、代入演算子が呼び出され、代入演算子の戻り値は通常は自身への参照ですから左辺値です。
    ユーザ定義の代入演算子は右辺値に対しても呼び出せるということなのだろうと思います。

    キャンセル

+1

引数にコンストラクタを渡している例や説明をあまりにも見かけないので心配になり質問しました.

コンストラクタは渡されていません。その場で生成したインスタンスを渡しているだけです。


なお、std::vectorを引数に取ると、毎回コピーされます。狙ってやるのならともかく、コピー処理が不要なら、参照引数としたほうがいいでしょう(const std::vector<int> & vecのようにすれば、参照経由での書換も不可能になります)。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/06/26 08:31

    語弊を生む単語の使い方をしてしまいました.その場で生成したインスタンスを関数に渡してもそのインスタンスの生存期間は問題にならないですし,結論はOKということでいいんでしょうか.あと,参照引数にしなかったのは簡単化のためです.ご丁寧にありがとうございます.

    キャンセル

  • 2019/06/26 08:33

    他で同じインスタンスを使わないなら、引数で使うときに新しいインスタンスを作っても特に問題はありません。

    キャンセル

  • 2019/06/26 09:48

    追記2は関数宣言になるので要注意。{}を使うべきですね。

    キャンセル

0

私は場合分けを減らすためにしばしば引数にコンストラクタを渡します.

コンストラクタを渡してはいません。

呼び出し側でコンストラクタによって初期化された一時オブジェクトを
呼び出された func の vec が共用してアクセスします。

呼び出し側でコンストラクタによって初期化された一時オブジェクトを使い、
コピーコンストラクタで func の仮引数 vec を初期化するということでは
ありません。

func の vec を呼び出し側のコンストラクタで初期化すると解釈しても構いません。

引数にコンストラクタのインスタンスを渡している例や説明をあまりにも見かけないので心配になり質問しました.

<algorithm> の sort を使って vector<int> を降順にソートするとき、
sort(vec.begin(), vec.end(), greater<int>()); と書きます。

greater<int>() は、greater<int>クラスのインスタンスで、このクラスには、
bool operator()(int a, int b) { return a > b; }
いうメンバ関数が定義されてるので、
greater<int>() は関数オブジェクトと呼ばれます。

vector<int> vec;
func(vec);


この場合、呼び出し側の vec の値を使い、コピーコンストラクタで
func の vec を初期化します。
値渡しです。変数渡しではありません。

void func(vector<int>& vec) { と宣言されていれば、
参照渡しであり、これを変数渡しといっても構いません。

vector<int> vec; で宣言された vec は左辺値です。
vector<int>() で生成された一時オブジェクトですら左辺値です。
だから、次のプログラムはエラーになりません。

#include <vector>

int main()
{
    std::vector<int>() = { 1, 2, 3 };
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 88.80%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る