Warum verhindert #include <string> hier einen Stapelüberlauffehler?

121

Dies ist mein Beispielcode:

#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;
}

Wenn ich #include <string>auskommentiere, bekomme ich keinen Compilerfehler, denke ich, weil er irgendwie durch enthalten ist #include <iostream>. Wenn ich in Microsoft VS mit der rechten Maustaste auf -> Zur Definition gehe, klicken beide auf dieselbe Zeile in der xstringDatei:

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

Aber wenn ich mein Programm starte, erhalte ich einen Ausnahmefehler:

0x77846B6E (ntdll.dll) in OperatorString.exe: 0xC00000FD: Stapelüberlauf (Parameter: 0x00000001, 0x01202FC4)

Irgendeine Idee, warum ich beim Auskommentieren einen Laufzeitfehler bekomme #include <string>? Ich verwende VS 2013 Express.

in der Luft
quelle
4
Mit Gottes Gnade. Arbeiten Sie perfekt auf gcc, siehe ideone.com/YCf4OI
v78
Haben Sie Visual Studio mit Visual C ++ ausprobiert und <string> auskommentiert?
Luft
1
@cbuchart: Obwohl die Frage bereits beantwortet wurde, denke ich, dass dies ein so komplexes Thema ist, dass es wertvoll ist, eine zweite Antwort in anderen Worten zu haben. Ich habe dafür gestimmt, Ihre großartige Antwort wiederherzustellen.
Leichtigkeitsrennen im Orbit
5
@ Ruslan: Tatsächlich sind sie. Das heißt, #include<iostream>und <string>beide könnten einschließen <common/stringimpl.h>.
MSalters
3
In Visual Studio 2015 erhalten Sie eine Warnung ...\main.cpp(23) : warning C4717: 'operator<<': recursive on all control paths, function will cause runtime stack overflowbeim Ausführen dieser Zeilecl /EHsc main.cpp /Fetest.exe
CroCo

Antworten:

161

In der Tat sehr interessantes Verhalten.

Irgendeine Idee, warum ich beim Auskommentieren einen Laufzeitfehler bekomme #include <string>

Mit dem MS VC ++ - Compiler tritt der Fehler auf, denn wenn Sie dies nicht tun, haben #include <string>Sie keine operator<<Definition für std::string.

Wenn der Compiler versucht zu kompilieren ausgabe << f.getName();, sucht er nach einem operator<<definierten für std::string. Da es nicht definiert wurde, sucht der Compiler nach Alternativen. Es gibt ein operator<<definiertes für MyClassund der Compiler versucht, es zu verwenden, und um es zu verwenden, muss es konvertiert werden std::string, MyClassund genau das passiert, weil MyClasses einen nicht expliziten Konstruktor hat! Der Compiler erstellt also eine neue Instanz von Ihnen MyClassund versucht, diese erneut in Ihren Ausgabestream zu streamen. Dies führt zu einer endlosen Rekursion:

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

Um den Fehler zu vermeiden, müssen Sie #include <string>sicherstellen, dass ein operator<<definiert für ist std::string. Außerdem sollten Sie Ihren MyClassKonstruktor explizit angeben, um diese Art der unerwarteten Konvertierung zu vermeiden. Weisheitsregel: Machen Sie Konstruktoren explizit, wenn sie nur ein Argument verwenden, um eine implizite Konvertierung zu vermeiden:

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

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

Es sieht aus wie operator<<für std::stringnur definiert wird , wenn <string>(mit dem MS - Compiler) enthalten ist und für , dass alles compiles Grund, aber Sie etwas unerwartetes Verhalten bekommen , wie operator<<rekursiv für immer genannt , MyClassanstatt Aufruf operator<<für std::string.

Bedeutet das, dass Through #include <iostream>String nur teilweise enthalten ist?

Nein, die Zeichenfolge ist vollständig enthalten, da Sie sie sonst nicht verwenden könnten.

Pavel P.
quelle
19
@airborne - Es handelt sich nicht um ein "Visual C ++ - spezifisches Problem", sondern darum, was passieren kann, wenn Sie nicht den richtigen Header einfügen. Bei Verwendung std::stringohne #include<string>alles kann passieren, nicht nur auf einen Kompilierungszeitfehler beschränkt. Das Aufrufen der falschen Funktion oder des falschen Operators ist anscheinend eine andere Option.
Bo Persson
15
Nun, das ist nicht "Aufruf der falschen Funktion oder des falschen Operators"; Der Compiler macht genau das, was Sie ihm gesagt haben. Du wusstest einfach nicht, dass du es dazu aufgefordert hast;)
Lightness Races in Orbit
18
Die Verwendung eines Typs ohne Angabe der entsprechenden Header-Datei ist ein Fehler. Zeitraum. Könnte die Implementierung das Erkennen des Fehlers erleichtert haben? Sicher. Dies ist jedoch kein "Problem" bei der Implementierung, sondern ein Problem mit dem von Ihnen geschriebenen Code.
Cody Gray
4
Standardbibliotheken können Token einschließen, die an anderer Stelle in std in sich selbst definiert sind, und müssen nicht den gesamten Header einschließen, wenn sie ein Token definieren.
Yakk - Adam Nevraumont
5
Es ist etwas humorvoll zu sehen, wie einige C ++ - Programmierer argumentieren, dass der Compiler und / oder die Standardbibliothek mehr Arbeit leisten sollten, um ihnen zu helfen. Die Implementierung liegt hier gemäß dem Standard, wie bereits mehrfach erwähnt, im Rahmen ihrer Rechte. Könnte "Trick" verwendet werden, um dies für den Programmierer offensichtlicher zu machen? Sicher, aber wir könnten auch Code in Java schreiben und dieses Problem insgesamt vermeiden. Warum sollte MSVC seine internen Helfer sichtbar machen? Warum sollte ein Header eine Reihe von Abhängigkeiten ziehen, die er eigentlich nicht benötigt? Das verletzt den ganzen Geist der Sprache!
Cody Gray
35

Das Problem ist, dass Ihr Code eine unendliche Rekursion ausführt. Der Streaming-Operator für std::string( std::ostream& operator<<(std::ostream&, const std::string&)) wird in der <string>Header-Datei deklariert , obwohl er std::stringselbst in einer anderen Header-Datei deklariert ist (eingeschlossen in <iostream>und <string>).

Wenn Sie nicht einschließen, <string>versucht der Compiler, einen Weg zum Kompilieren zu finden ausgabe << f.getName();.

Es kommt vor, dass Sie sowohl einen Streaming-Operator für MyClassals auch einen Konstruktor definiert haben, der a zulässt std::string, sodass der Compiler ihn verwendet (durch implizite Konstruktion ) und einen rekursiven Aufruf erstellt.

Wenn Sie explicitIhren Konstruktor ( explicit MyClass(const std::string& s)) deklarieren, wird Ihr Code nicht mehr kompiliert, da es keine Möglichkeit gibt, den Streaming-Operator mit aufzurufen std::string, und Sie gezwungen sind, den <string>Header einzuschließen.

BEARBEITEN

Meine Testumgebung ist VS 2010 und ab Warnstufe 1 ( /W1) werden Sie vor dem Problem gewarnt :

Warnung C4717: 'operator <<': rekursiv auf allen Steuerpfaden, Funktion verursacht Laufzeitstapelüberlauf

cbuchart
quelle