テンポラリオブジェクトの寿命は、const参照で初期化していない場合、そのオブジェクトを定義した文だけです。
それにもかかわらず、base.Show()が呼び出せます。
なぜ、呼び出せるのでしょうか。
コンパイラ:Visual Studio Community 2015
OS:Windows7 64bit
C++
1#include <iostream> 2 3class CBase { 4public: 5 void Show() { 6 std::cout << "Show" << std::endl; 7 } 8}; 9 10int main() { 11 CBase& base = CBase(); 12 base.Show(); 13}
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答5件
0
ベストアンサー
###Visual Studioの言語拡張でした
Visual Studioで動作を確認しました。どうやら、最初に思っていたのと違ったようです。
問題なく動作するのはVisual Studioの言語拡張であり、標準のC++としては違反になります。"/W4"で警告レベルを上げると警告が表示され、"/Za"で言語拡張を無効化するとエラーになります。下記を参考にして下さい。(拡張としては、constありと同じように寿命が延びるようになるようです。constありの場合の話は後述。)
- Temporary Objects Can be Bound to Non-Const References | Microsoft Connect
- c++ - Non-const reference bound to temporary, Visual Studio bug? - Stack Overflow
- c++ - Visual Studio is not creating temporary object when typecasting? - Stack Overflow
- コンパイラの警告 (レベル 4) C4239
C++標準ではないVisual Studio特有の仕様ですので、Visual Studioを永遠に使い続けるのであれば問題ありません。他のコンパイラでも動作できるように移植する予定であれば、このような記述は避けるべきでしょう。このままではClangやGCCではエラーになってコンパイルできません。
###constな左辺値参照はテンポラリオブジェクトの寿命を延ばす
const CBase& base = CBase();
とconstがついている場合は(メンバー関数もconstにする必要がありますが)、参照が存在する間だけオブジェクトの寿命が延びるため、標準のC++として問題ありません。これは式が終了しても、テンポラリオブジェクトが破棄されない例外の一つです。
参考: Object lifetime - cppreference.com
The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.
constな左辺値参照__または右辺値参照(C++11から)__に紐付けられる場合、テンポラリオブジェクトの寿命は延長され得る。詳しくは参照の初期化を見よ。
上記の記述にあるように参照が生き残っている間は、テンポラリオブジェクトは破棄されず、使い続ける事が出来ます。C++標準の仕様ですので、**使ってはいけないわけではありません。**ただ、混乱の元になりそうですので、ムーブコンストラクタを実装して、通常のオブジェクトとして変数を確保し、寿命をはっきりさせた方が良いでしょう。
###そもそもオブジェクトの破棄とはアクセスできなくなることではない
さて、話は変わりますが、テンポラリオブジェクトが破棄されると言っても、テンポラリオブジェクトによって使われていたスタック上のメモリ領域が0埋めされるとか、該当メモリ領域へのアクセスが拒否が起きるとか、そういうことではありません。スタック上に確保されたメモリ領域は、他に再利用されない限りそのままであり、無理矢理使用することは可能です。しかし、言語の仕様上、その領域がいつまでそのままになっているかは一切保証がありません。つまり、このメモリ領域はもう使う予定は無いから再利用してもいいよというのが、寿命がつきて破棄されるということです。もし、破棄された後とその利用の間に何か別の処理を追加してあった場合、それだけでテンポラリオブジェクトに使っていたスタック上のメモリ領域が上書きされ、おかしな事が起きる可能性があります。これは、関数が終わった後に破棄されたローカル変数や、deleteやfreeでのメモリ解放でも同じ事が言えます。
なお、コンパイラの最適化もあるため、実際上書きされるようなコードになるかはわかりません。逆に、間に処理が無くても、最適化によって、上書きされるようなコードが挿入される場合があります。また、デバッグ機能を使っているときは、バグ出しのためにそのような上書きコードが随時挟まれる場合があります。どうなるかはコンパイラやその時のオプション次第です。
参考: deleteしても使えてしまう例。何が起こるかはコンパイラやオプションによって異なります。
C++
1#include <iostream> 2#include <string> 3 4class CBase { 5private: 6 std::string show; 7public: 8 CBase() { 9 show = "Show"; 10 }; 11 void Show() { 12 std::cout << show << std::endl; 13 } 14}; 15 16int main() { 17 auto base = new CBase(); 18 delete base; 19 auto pstr = new std::string("None"); 20 base->Show(); 21}
投稿2016/02/06 10:39
編集2016/02/06 19:15総合スコア21733
0
VC++ が緩いだけで g++ ではデフォルトでコンパイルエラーになります。
$ cat sample.cpp #include <iostream> class CBase { public: void Show() { std::cout << "Show" << std::endl; } }; int main() { CBase& base = CBase(); base.Show(); } $ g++ -o sample sample.cpp sample.cpp: In function ‘int main()’: sample.cpp:10:23: error: invalid initialization of non-const reference of type ‘CBase&’ from an rvalue of type ‘CBase’ CBase& base = CBase(); ^ $
投稿2016/02/06 10:29
編集2016/02/06 10:33総合スコア1193
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/02/06 14:38
2016/02/06 14:47
0
**void Show()はvirtualではないので実際は現在なコンパイラーにとって普通の関数にすぎないものです。というとそのオブジェクトのメモリーに一切触ろうとしないで、直接Show()**を呼び出すだけです。そして、**Show()**の中からオブジェクトのメモリも触れていませんので、ランタイムエラーにもなりません。
ゼロポインタで呼ぼうとしても何も変わりません。
Assemlerを見てみれば、それをはっきり見えるのです。
> cat test.cpp #include <iostream> class CBase { public: void Show() { std::cout << "Show" << std::endl; } }; int main() { CBase *base = 0; base->Show(); } > g++ test.cpp -O2 -fno-inline > ./a.out Show > g++ test.cpp -S -O2 -fno-inline -o test.S > cat test.S | c++filt 略 main: .LFB995: .cfi_startproc subq $8, %rsp .cfi_def_cfa_offset 16 call CBase::Show() ≪ー 直接Show()を呼び出すところ 略
逆に、**Show()**をvirtualに変えれば、オブジェクトの仮想メソッドテーブルをアクセスするのが必要なので、Segmentation Faultが起こります。
> cat test.cpp #include <iostream> class CBase { public: virtual void Show() { std::cout << "Show" << std::endl; } }; int main() { CBase *base = 0; base->Show(); } > ./a.out Segmentation fault (core dumped) > g++ test.cpp -O2 -fno-inline -S -o test.S > cat test.S | c++filt main: .LFB995: .cfi_startproc subq $8, %rsp .cfi_def_cfa_offset 16 movq 0, %rax xorl %edi, %edi call *(%rax) ≪ー ここはメモリーからShow()のアドレスを引き出して、それをコールしようとするが、baseがゼロなのでエラーが起こります。
そして**Show()**がオブジェクトのメモリーをアクセスしようとしたら、virtualでなくてもさすがにSegfaultになります。
> cat test.cpp #include <iostream> class CBase { int a; public: void Show() { std::cout << "Show" << a << std::endl; } }; int main() { CBase *base = 0; base->Show(); } > g++ test.cpp -O2 -fno-inline > ./a.out Segmentation fault (core dumped)
投稿2016/02/06 15:53
編集2016/02/06 15:58総合スコア12
0
this
ポインタへのアクセスがない限り、呼び出しても問題にはなりません。
CBase& base = *((CBase*)0);
こうしても動いてしまいますよ。
ただし、たまたま動くというだけであって、このようなコードは書くべきではありませんけどね。
投稿2016/02/06 10:32
総合スコア5938
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/02/06 14:37
2016/02/07 05:58
2016/02/07 11:23
0
こんにちは。
メンバ変数にアクセスしていないからですね。
CBase& base = CBase();
でオブジェクトが作られますが、中身は空です。
そして、おっしゃる通り、この文を抜けると破棄されます。msvcのデバッグ・モードならたぶん0xddd...のような値で埋められると思います。
base.Show()でその破棄されたオブジェクトがShow()にコッソリ渡されてます。
Show()の中では、メンバ変数にアクセスしていません(存在しないので当たり前ですが)ので、動作するのだと思います。
C/C++ってそんな言語です。
投稿2016/02/06 10:00
編集2016/02/06 10:01総合スコア23272
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/02/06 14:37
2016/02/06 19:16
2016/02/07 11:33