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

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

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

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

Visual Studio

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

C++

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

Q&A

解決済

5回答

6591閲覧

テンポラリオブジェクトへの参照を使用してメンバ関数が呼べてしまう

JADEN

総合スコア106

C++11

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

Visual Studio

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

C++

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

1グッド

0クリップ

投稿2016/02/06 09:51

テンポラリオブジェクトの寿命は、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}
Chironian👍を押しています

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

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

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

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

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

guest

回答5

0

ベストアンサー

###Visual Studioの言語拡張でした
Visual Studioで動作を確認しました。どうやら、最初に思っていたのと違ったようです。

問題なく動作するのはVisual Studioの言語拡張であり、標準のC++としては違反になります。"/W4"で警告レベルを上げると警告が表示され、"/Za"で言語拡張を無効化するとエラーになります。下記を参考にして下さい。(拡張としては、constありと同じように寿命が延びるようになるようです。constありの場合の話は後述。)

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
raccy

総合スコア21733

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

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

JADEN

2016/02/06 14:37

回答ありがとうございます。
raccy

2016/02/06 19:16

const無しでも動くのはVSの言語拡張でした。そんでもって、寿命が延びるのはC++の言語仕様でした。なので、色々修正していますので、ご確認下さい。
JADEN

2016/02/07 11:33

コンパイラ依存の書き方はしたくないので、非常に勉強になりました。
guest

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
kozuchi

総合スコア1193

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

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

Chironian

2016/02/06 11:02

ちなみに、constつけたらg++でも通ってしまいます。 #include <iostream> class CBase { public: void Show() const { std::cout << "Show" << std::endl; } }; int main() { CBase const& base = CBase(); base.Show(); } ま、constつけようがつけまいが、やるべきではないですね。
JADEN

2016/02/06 14:38

回答ありがとうございます。 Chironianさんへ 例外的に、const参照の場合は、自動変数と同じ寿命になるので大丈夫だと思います。
Chironian

2016/02/06 14:47

なるほど。ありがとうございます。 そういえば右辺値参照が保持するインタンスのスコープも自動変数と同様と聞いた記憶があります。const参照≒右辺値参照なのでそういうことなのですね。
guest

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
Nikolay

総合スコア12

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

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

JADEN

2016/02/07 11:26

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

0

thisポインタへのアクセスがない限り、呼び出しても問題にはなりません。
CBase& base = *((CBase*)0);
こうしても動いてしまいますよ。
ただし、たまたま動くというだけであって、このようなコードは書くべきではありませんけどね。

投稿2016/02/06 10:32

catsforepaw

総合スコア5938

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

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

JADEN

2016/02/06 14:37

回答ありがとうございます。 なぜ、そのコードでも動くのでしょうか。
catsforepaw

2016/02/07 05:58

クラスオブジェクトのメンバ関数呼び出しは、内部的にはオブジェクトのポインタを`this'に入れて関数を呼び出していることになります。 C言語風に書くとこんな感じになります。 ```C void Show(CBase* this) { // Showの処理 } int main() { CBase base; Show(&base); ``` Show関数の中でthisを使わなければ、何を渡されようともShow関数は問題なく動きますよね。
JADEN

2016/02/07 11:23

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

0

こんにちは。

メンバ変数にアクセスしていないからですね。

CBase& base = CBase();でオブジェクトが作られますが、中身は空です。
そして、おっしゃる通り、この文を抜けると破棄されます。msvcのデバッグ・モードならたぶん0xddd...のような値で埋められると思います。
base.Show()でその破棄されたオブジェクトがShow()にコッソリ渡されてます。
Show()の中では、メンバ変数にアクセスしていません(存在しないので当たり前ですが)ので、動作するのだと思います。

C/C++ってそんな言語です。

投稿2016/02/06 10:00

編集2016/02/06 10:01
Chironian

総合スコア23272

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

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

JADEN

2016/02/06 10:07 編集

回答ありがとうございます。 空のオブジェクトとは、メンバ変数がないという意味ですか? メンバ変数を宣言して、Show関数で表示するようにしましたが、まだ呼び出せてしまいます。
Chironian

2016/02/06 10:08

あ、ごめんなさい。回答を修正しました。 メンバ変数をアクセスしていないので大丈夫なのです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問