調子に乗ったらダングリングした話

# はじめに

気を付けていても、ふとした瞬間に右辺値参照の寿命を切らすことがあります。
これはコンパイラの種別・バージョン・最適化オプションによって、たまたま動いたり動かなかったりするので結構しんどい問題です。

今回、平成最後のアドベントカレンダーということもあり、いや関係ないですが、恥を忍んで実際に寿命を切らしてしまった事例と対策を紹介します。

# 事例1: std::minmax で殺られた

```cpp
    auto mm = std::minmax(5, 6);
    std::cout << "min:" << mm.first << " max:" << mm.second << std::endl;
```

結果はどうなるでしょうか？ `min:5 max:6` が印字されそうですが...


答えは不定です。
http://rextester.com/DKK11270

## どうしてこうなった

std::minmax のシグニチャはこうです:

```cpp
template< class T >
std::pair<const T&,const T&> minmax( const T& a, const T& b );
```

引数で与えた数値 5, 6 はrvalue（メモリ上に実体がない値)オブジェクトです。
これが`const int&` で参照されたので、この`minmax()`を呼び出している文の終わり、つまり次のセミコロンまでは、寿命が伸ばされます。

さて、`minmax()`の中に入りました。数値5と6への参照をさらに`str::pair`のコンストラクタに参照として渡します。

```cpp
template<typename T>
pair<const T&, const T&>
minmax(const T& a, const T& b)
{
    return b < s ? pair<const T&, const T&>(b, a) : pair<const T&, const T&>(a, b);
}
```

作られたpairオブジェクトは、渡された 数値への参照をどうしているかというと...

```cpp
template<typename T1, typename T2>
struct pair
{
    T1 first;
    T2 second;
    :
};
```

今、型T1, T2は`const int&`なので、実体を保持していません。

intの数値実体を持っていないpairオブジェクトが`minmax()`から返され、それが`auto mm`にコピーされます。

```cpp
auto mm = std::minmax(5, 6);
```

この mm は実際にはどんな形になるかというと、`minmax()`のシグニチャより`std::pair<const T&, const T&>` です。やはり実体持っていません。

そうこうしているうちに `minmax()`呼び出し行が終了し、 一時オブジェクトの 5 と 6 が破棄されました。

mmは破棄された 5, 6の跡地を参照していますので、

```cpp
    std::cout << "min:" << mm.first << " max:" << mm.second << std::endl;
```

この結果がどうなるかは運次第、という状況になります。

一連の流れをザラッと図にしてみました。

![auto.png](https://qiita-image-store.s3.amazonaws.com/0/115808/b3ec0974-d1d4-d96a-8a6c-9871e1586090.png)

## 直し方

const参照のpairでなく実体のpairで受けると、文が終わる前にコピーされるので大丈夫になります。(上の図で言うと4のタイミングで実体がコピーされる)

```cpp
    std::pair<int, int> mm = std::minmax(5, 6);
    std::cout << "min:" << mm.first << " max:" << mm.second << std::endl;
```
ちなみに`std::min`, `std::max` だったら、`auto`で受けても大丈夫です。
`std::min()` の戻り値 `const T&` を`auto` でうけると、`auto`は `T`に推論(今の例で言えば`int`に推論)され、値がコピーされることになります。

## 実際のしにざま

きれいな顔してるだろ。ウソみたいだろ。死んでるんだぜ。

```cpp
int main()
{
    std::vector<int> v1{0,1,2,3,4};    // size(): 5
    std::vector<int> v2{5,6,7,8,9,10}; // size(): 6

    auto mm = std::minmax(v1.size(), v2.size());
    std::cout << "min:" << mm.first << " max:" << mm.second << std::endl;
```

## 対策

コンパイラは警告を吐いてくれませんでした。
が、gcc7では以下のオプションでランタイムエラーを吐いてくれました。
単体テストなりで1回通せば気づけます。

```shell-session
g++-7 -fsanitize=address -fsanitize-address-use-after-scope
```

## みんな知ってたの？

ちゃんと書いてあります...

http://en.cppreference.com/w/cpp/algorithm/minmax

>Notes
>
>For overloads (1,2), if one of the parameters is an rvalue, the reference returned becomes a dangling
>reference at the end of the full expression that contains the call to minmax:


ここはまずいですね。。。
http://kaworu.jpn.org/cpp/std::minmax

おお、ここはautoで受けていない！老獪か！
https://cpprefjp.github.io/reference/algorithm/minmax.html

# 事例2: メソッドチェインでやられる

```cpp
struct Test {
    Test& inc() {
        ++value;
        return *this;
    }

    int value = 1;
};

int main() {
    auto&& t = Test().inc();
    std::cout << t.value << std::endl;

    return 0;
}
```

結果はどうなるでしょうか？ 2が返ってきそうですが。。。
gccでは0になりました。

http://rextester.com/ADV58146

## アライさんまた殺ってしまったね

順に追っていきましょう。
まず、下記は右辺値参照です。Test()は Testオブジェクトの一時オブジェクトを作り、それがtに束縛さるので、この後もちゃんと使えます。

```cpp
    auto&& t = Test(); // OK. decltype(t) == Test&&
```

つぎに、調子に乗ってinc()メソッドも呼んでみます。

```cpp
    auto&& t = Test().inc(); // NG. decltype(t) == Test&
```

一時オブジェクトは、文が終わるまで有効ですから、inc()は安全に呼べます。
そのinc()は Test& 型を返します。
`auto&&`の型決定時、これは一時オプジェクトではなく、実体のある左辺値への参照として解釈され、 t も左辺値への参照、すなわち Test& となります。

その結果、誰も Testオブジェクトを持つ人がいなくなるため、

```cpp
    std::cout << t.value << std::endl; // ｶﾞｯ！
```

残念な結果に終わります。

## 直し方

素直に分けましょう。

```cpp
    auto t = Test():
    t.inc();
```
2017-12-10追記: コメント欄にて、@yumetodoさんからref qualifierを使って安全にmoveするやり方を教えて頂きました。自分的に咀嚼し切れてないので、とりあえずベストな選択は各自の演習とします:pray:

2018-01-20追記: 上記指摘頂いた方法でも問題ありとのことです。
ありがとうございます＞ @akinomyoga さん @yumetodo さん
例によって私自身、規格の理解が追いついていないので、とりあえずベス(ry

頑張って理解後、説明書きたい...:cat:

## 対策

事例1と同様、gccのランタイムチェッカにお願いすると、優しく殺してくれます。

# まとめ

今回、ダングリング参照が起こる事例を紹介しました。
C++初心者向けカレンダーにふさわしいのかどうか[分かりません](https://qiita.com/yumetodo/items/a843bd542106215bbc84)が、実際問題として、知らないと致命的な不具合の元になる内容です。

コンパイラやvalgrindなどのツールを活用して、安全なコードを書いていきましょう。

御安全に！