#include <string>がスタックオーバーフローエラーを防ぐのはなぜですか?


121

これは私のサンプルコードです:

#include <iostream>
#include <string>
using namespace std;

class MyClass
{
    string figName;
public:
    MyClass(const string& s)
    {
        figName = s;
    }

    const string& getName() const
    {
        return figName;
    }
};

ostream& operator<<(ostream& ausgabe, const MyClass& f)
{
    ausgabe << f.getName();
    return ausgabe;
}

int main()
{
    MyClass f1("Hello");
    cout << f1;
    return 0;
}

コメントアウトして#include <string>もコンパイラエラーは発生しませんが、それはを介してインクルードされているため#include <iostream>です。私の場合、「右クリック- >定義に移動し、」マイクロソフトVSにおけるこれらの同じ行に両方のポイントxstringのファイル:

typedef basic_string<char, char_traits<char>, allocator<char> >
    string;

しかし、プログラムを実行すると、例外エラーが発生します。

OperatorString.exeの0x77846B6E(ntdll.dll):0xC00000FD:スタックオーバーフロー(パラメーター:0x00000001、0x01202FC4)

コメントアウトするとランタイムエラーが発生するのはなぜ#include <string>ですか?VS 2013 Expressを使用しています。


4
神の恵みをもって。gccで完全に動作します。ideone.com
YCf4OIを

Visual C ++でVisual Studioを試して、include <string>をコメントアウトしましたか?
空中

1
@cbuchart:質問はすでに回答されていますが、これは複雑なトピックなので、別の言葉で2番目の回答をすることは価値があります。私はあなたの素晴らしい答えを元に戻すことに投票しました。
オービットでの軽さのレース

5
@ルスラン:事実、そうです。それは言うことです、#include<iostream><string>の両方が含まれる場合があります<common/stringimpl.h>
MSalters 2017年

3
Visual Studio 2015では、...\main.cpp(23) : warning C4717: 'operator<<': recursive on all control paths, function will cause runtime stack overflowこの行を実行すると警告が表示されますcl /EHsc main.cpp /Fetest.exe
CroCo

回答:


161

確かに、非常に興味深い行動。

コメントアウトするときにランタイムエラーが発生する理由 #include <string>

MS VC ++コンパイラを使用すると、エラーが発生します。これ#include <string>を行わないと、をoperator<<定義していないためstd::stringです。

コンパイラがコンパイルを試みるとausgabe << f.getName();operator<<定義されているを探しstd::stringます。定義されていないため、コンパイラは代替を探します。operator<<for が定義されてMyClassおり、コンパイラーはそれを使用しようとし、それを使用するには変換するstd::string必要がMyClassあります。これはMyClass、明示的なコンストラクターがないため、まさに何が起こるかです。したがって、コンパイラは最終的にの新しいインスタンスを作成し、MyClassそれを再び出力ストリームにストリーミングしようとします。これは無限の再帰をもたらします:

 start:
     operator<<(MyClass) -> 
         MyClass::MyClass(MyClass::getName()) -> 
             operator<<(MyClass) -> ... goto start;

エラーを回避する#include <string>には、のoperator<<定義があることを確認する必要がありstd::stringます。またMyClass、この種の予期しない変換を回避するには、コンストラクターを明示的にする必要があります。知恵のルール:コンストラクターが引数を1つだけ取る場合は明示的にして、暗黙的な変換を回避します。

class MyClass
{
    string figName;
public:
    explicit MyClass(const string& s) // <<-- avoid implicit conversion
    {
        figName = s;
    }

    const string& getName() const
    {
        return figName;
    }
};

operator<<for std::string<string>含まれている場合にのみ定義されているように見え(MSコンパイラーを使用)、そのためすべてがコンパイルされますが、operator<<for MyClassを呼び出す代わりにに対して再帰的に呼び出されるため、予期しない動作が発生operator<<std::stringます。

それは、スルー#include <iostream>文字列が部分的にのみ含まれることを意味しますか?

いいえ、文字列は完全に含まれます。それ以外の場合は使用できません。


19
@airborne-これは「Visual C ++固有の問題」ではありませんが、適切なヘッダーを含めない場合に起こり得ることです。コンパイル時のエラーに限らstd::stringず、#include<string>あらゆる種類のことをせずに使用すると発生する可能性があります。間違った関数または演算子を呼び出すことは、明らかに別のオプションです。
Bo Persson

15
まあ、これは「間違った関数や演算子を呼び出す」ことではありません。コンパイラーは、ユーザーが指示したとおりに実行しています。あなたはこれをするように言っていることを知らなかっただけです;)
オービットのライトネスレース

18
対応するヘッダーファイルを含めずに型を使用すると、バグが発生します。限目。実装により、バグを特定しやすくなったでしょうか?承知しました。しかし、これは実装の「問題」ではなく、作成したコードの問題です。
コーディグレイ

4
標準ライブラリは、標準ライブラリ内の他の場所で定義されているトークンを自由に含めることができ、1つのトークンを定義する場合、ヘッダー全体を含める必要はありません。
Yakk-Adam Nevraumont 2017年

5
多くのC ++プログラマがコンパイラや標準ライブラリ、あるいはその両方を支援するためにさらに作業を行うべきだと主張するのを見るのは、やや滑稽です。何度も指摘されているように、標準によると、実装はここで十分にその権利の範囲内です。「トリッキー」を使用して、プログラマーがこれをより明確にすることができますか?もちろん、Javaでコードを記述して、この問題を完全に回避することもできます。MSVCが内部ヘルパーを表示する必要があるのはなぜですか?ヘッダーが実際には必要のない一連の依存関係をドラッグする必要があるのはなぜですか?それは言語の精神全体に違反します!
コーディグレイ

35

問題は、コードが無限再帰を行っていることです。std::stringstd::ostream& operator<<(std::ostream&, const std::string&))のストリーミング演算子は<string>ヘッダーファイルで宣言されていますが、std::stringそれ自体は他のヘッダーファイルで宣言されています(<iostream>およびの両方に含まれています<string>)。

含めない場合<string>、コンパイラはコンパイルする方法を見つけようとしますausgabe << f.getName();

ストリーミングオペレーターMyClassとを許可するコンストラクターの両方を定義したstd::stringため、コンパイラーはそれを(暗黙の構築を通じて)使用し、再帰呼び出しを作成します。

explicitコンストラクター(explicit MyClass(const std::string& s))を宣言すると、ストリーミングオペレーターをstd::stringで呼び出す方法がないため、コードはコンパイルできなくなり、<string>ヘッダーを含める必要があります。

編集

私のテスト環境はVS 2010で、警告レベル1(/W1)から始めて、問題について警告します。

警告C4717: 'operator <<':すべての制御パスで再帰的、関数によりランタイムスタックオーバーフローが発生します

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.