C++得意な方へ質問なのですが、何故 非constな参照を一時オブジェクトで初期化できないのでしょうか?


class A
{
public:
A() {}
~A() {}
};

void test(A& a)
{
a.method();
}

void test_main()
{
test(A()); // <- この行
}

ここで、test(A())の行で、
initial value of reference to non-const must be an lvalue
というエラーが出てしまいます。const参照ならば、当然OKですが このようになっている理由が知りたいです。

回答の条件
  • 1人2回まで
  • 登録:2006/12/26 14:44:53
  • 終了:2007/01/02 14:45:34

回答(4件)

id:Bookmarker No.1

しおり回答回数191ベストアンサー獲得回数342006/12/26 18:49:41

ポイント27pt

非const参照は、変更したオブジェクトを呼び出し側に返す事を期待しているはずなので、一時オブジェクトを渡しているのはバグだろうと気を利かせているだけだと思います。

id:suzume_oyado

期待はずれにならないようにという配慮ですね? 優しい…。

2006/12/27 00:15:45
id:ymiz777 No.2

ymiz777回答回数31ベストアンサー獲得回数12006/12/26 23:36:48

ポイント27pt

C++の関数は、オブジェクトのリファレンスを戻り値にすることがありますが、当然、その関数の引数として与えられたオブジェクトのリファレンスを戻すこともできるので、引数に一時オブジェクトを指定できしまうと、戻り値を受ける側が、何もないオブジェクトを指し示してしまうということを避けるための措置ではないかと思います。

ただ、このように考えると、逆に、const参照であれば、認められる方が不思議になってしまうかもしれませんが、const参照だけは、なぜか、一時オブジェクトの生存期間を、参照がなくなるまで、拘束できるという、一見、難しいルールがあります。

ご質問の内容に、近しい内容の説明をしていたページが、以下にありました。

http://www.sun-inet.or.jp/~yaneurao/intensive/cppmaniax/chap0001...

この中で、以下のlist-5を参照してもらうと、このルールの意味が理解できるのではないかと思います。

int& i = int(1); // error

const int& i = int(1); // ok

このokまで、errorにされては、たまりませんね。

--------

ちなみに、ご質問の文中にあったコードを、Visual C++ 2005で、コンパイルしようとすると、ノーエラーで、コンパイルできてしまいます。

ただ、以下のようなコードに変形をすると、確かに、test_main()関数の戻り値は、Aのデストラクタが呼び出された後に、returnをしていて、test_main()関数を呼び出した側では、無効なオブジェクトの参照を受けてしまっているようです。


class A

{

public:

A() {}

~A() {}

void method(void) {}

};

A& test(A& a)

{

a.method();

return a;

}


A& test_main()

{

return test(A()); // <- この行

}

id:suzume_oyado

気を利かせた説が有力…?

2006/12/27 00:21:46
id:mj99 No.3

mj99回答回数138ベストアンサー獲得回数382006/12/27 02:05:42

ポイント26pt

こうゆう例の方がわかりやすいかも。

----

void test(int& a)

{

a = a + 1;

}

void test_main()

{

test(10);

}

----

意味のないコードですよね。

コンパイラが気を利かせている説有力

(ちなみに上記のコードは、Microsoft C,Borland Cではエラーならず、警告になりました)

id:suzume_oyado

コード的に意味の無いコードは他にもいくらでもあるのに、この件についてだけ、エラーになるコンパイラがある理由がわかりません。何か、論理的に説明が付くものがあると思うのです。

2006/12/27 10:34:24
id:ccv No.4

ccv回答回数2ベストアンサー獲得回数02006/12/30 18:17:48

ポイント10pt

http://d.hatena.ne.jp/asin/475611895X

一時オブジェクトでの初期化を認めることによって発生するリスクについて『プログラミング言語C++第3版』に記載されています。

$7.2 引数渡し (P.189)

////////////////////////////////////////

void update(float& i);

void g(double d, float r)

{

update(2.0f); // エラー:定数引数

update(r); // rのリファレンスを渡す

update(d); // エラー:型変換が必要

}

これらの呼び出しが認められていたら、update()はすぐに削除されてしまう一時変数を何も言わずに更新していただろう。このようなことが起きたら、プログラマをびっくりさせるはずである。

////////////////////////////////////////

  • id:Bookmarker
    >>
    コード的に意味の無いコードは他にもいくらでもあるのに、この件についてだけ、エラーになるコンパイラがある理由がわかりません。何か、論理的に説明が付くものがあると思うのです。
    <<
    -constの目的はコレクトネス(correctness)である。(つまりコンパイラがバグを見つけるのが目的である。)
    -コンパイラが容易に検出できる。
    では足りませんか?
    ちなみに、プログラミング言語C++第3版(ISBN4-7561-1895-X)には次のように書かれています。
    「5.5 リファレンス」より
    >>
    定数のリファレンスと変数のリファレンスが区別されているのは、変数で一時変数を導入するとエラーが起きる可能性が高くなるためである。変数への代入は、すぐに消える一時変数への代入になる。定数のリファレンスにはそのような問題はない。
    <<
  • id:suzume_oyado
    おぉっ!!! ありがとうございます。 かなりわかってきました。
    同時に自分が何に引っかかっているのかもわかりました。

    「一時オブジェクトを非const参照で渡すようなシチュエーションがあったら一体どうしてくれるんだっ!!」

    と、いう事です。で、考えてみたのですが、そんな状況がさっぱり、思いつきませんでした。
    やはり、そんな使い方は間違いなく誤りなんでしょうか…。くやしい…。悔しすぎる (T_T)

    (後で、ポイント送付させて下さい。)
  • id:Bookmarker
    回答2は勘違いされていると思います。

    const int& i = int(1);

    が OK なのは、オブジェクトの寿命を考えた場合、

    int temp = int(1);
    const int &i = temp;

    と同じだからです。
    だから、

    int& i = int(1);

    を OK にしても、

    int temp = int(1);
    int &i = temp;

    と同じなので、別にコンパイルエラーにしなくてもいいけど、たぶんバグだよねという事でコンパイルエラーにしているだけでしょう。

    ちなみに、示されている Web ページの list-2 は、本質問と同じくコンパイルエラーになります。

    また、const参照で受けた引数を関数の復帰値としてconst参照で返すのは危険なので返してはならないといのは FAQ です。

    #include <string>

    const std::string &func(const std::string &s)
    {
    return s; // 危険
    }

    int main()
    {
    const std::string &s = func("abc"); // 消滅した一時オブジェクトを参照している
    }

    これこそコンパイルエラー(せめて警告)にして欲しいんだけど、コンパイルエラーになりませんね。
  • id:ymiz777
    申し訳ありませんでした。Bookmarkerさんの説明の通りに、私の回答は、全く、間違えです。
    一時オブジェクトを戻り値にしてはいけないのは、基本中の基本ですね。
  • id:Bookmarker
    # 少々古い話題になってしまいましたが…
    縁あって最近「[http://www.amazon.co.jp/gp/product/4797328541:title=C++の設計と進化]」という書籍の存在を知りました。
    本書は、C++の産みの親であるBjarne Stroustrup氏が自ら書き下ろした本で、「なぜそんな構造なのか」とか「どんな経緯でそうなったのか」というような事が書かれています。
    そして、本件の回答となる事も書かれていました。

    「3.7 リファレンス」より
    >>
    リファレンスは主に、演算子オーバロードをサポートするために導入された。
    (中略)
    私は、constでないリファレンスを非左辺値(即値など)で初期化させる、という重大な間違いを犯した。たとえば:

    void incr(int& rr) { rr++; }

    void g()
    {
    double ss = 1;
    incr(ss); // 注記:doubleが渡されたがintでなければならない
    // (修復:Release 2.0ではエラーになる)
    }

    タイプが違うので、渡されたdoubleをint&が参照することはできない。そこでssの値で初期化されたintを保持する一時変数が作られる。するとincr()はその一時変数を変えるので、その結果は呼び出し側に反映されない。
    リファレンスを非左辺値で初期化できるようにした理由は、値呼び出しとリファレンス呼び出しの区別が呼ばれた側の関数が指定する詳細で、呼び出し側には無関係、という状態にしたかったからだ。constリファレンスに関してはそれが可能だが、constでないリファレンスではできない。Release 2.0では、このことを反映してC++の定義が変えられた。
    <<

この質問への反応(ブックマークコメント)

トラックバック

  • srzw0326の日記 - 2007-12-12 11:46:37
    コメントではてな記法が使えない様なので日記にて。 ADL namespace srzw { struct Bar { int value_; }; void foo(Bar&amp; bar){} void foo(){} } int main() { srzw::Bar bar; foo(bar); // 省略可能 srzw::foo(); // 省略するとエラ
  • google-glogに潜むトリックを解明する  google-glogは非常に有名なロギングライブラリであり、その名前からわかる通りgoogleの人々によって開発されている。使い方は簡単で、 LOG(INFO) &lt;&lt; &quo
「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません