web-dev-qa-db-fra.com

Pourquoi #include <string> empêche-t-il une erreur de débordement de pile ici?

Ceci est mon exemple de code:

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

Si je commente #include <string>, Je ne reçois aucune erreur du compilateur, car elle est en quelque sorte incluse dans #include <iostream>. Si je "clic droit -> Aller à la définition" sous Microsoft VS, ils pointent tous deux vers la même ligne dans le fichier xstring:

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

Mais lorsque je lance mon programme, j'obtiens une erreur d'exception:

0x77846B6E (ntdll.dll) dans OperatorString.exe: 0xC00000FD: débordement de pile (paramètre: 0x00000001, 0x01202FC4)

Une idée de la raison pour laquelle je reçois une erreur d’exécution lorsque je commente #include <string>? J'utilise VS 2013 Express.

121
airborne

En effet, comportement très intéressant.

Toute idée de la raison pour laquelle je reçois une erreur d’exécution lorsque je commente #include <string>

Avec le compilateur MS VC++, l'erreur se produit car si vous ne le faites pas #include <string>, Vous n'avez pas operator<< Défini pour std::string.

Lorsque le compilateur tente de compiler ausgabe << f.getName();, il recherche un operator<< Défini pour std::string. Comme il n'a pas été défini, le compilateur recherche des alternatives. Il y a un operator<< Défini pour MyClass et le compilateur essaie de l'utiliser, et pour l'utiliser, il doit convertir std::string En MyClass et c'est exactement que se passe-t-il parce que MyClass a un constructeur non explicite! Ainsi, le compilateur finit par créer une nouvelle instance de votre MyClass et tente de la diffuser à nouveau dans votre flux de sortie. Cela se traduit par une récursion sans fin:

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

Pour éviter l'erreur, vous devez #include <string> Vérifier qu'il existe un operator<< Défini pour std::string. Aussi, vous devriez expliciter votre constructeur MyClass pour éviter ce genre de conversion inattendue. Règle de sagesse: explicite les constructeurs s'ils ne prennent qu'un seul argument pour éviter la conversion implicite:

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

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

Il semble que operator<< Pour std::string Ne soit défini que lorsque <string> Est inclus (avec le compilateur MS) et que pour cette raison tout est compilé, mais vous obtenez un comportement quelque peu inattendu en tant que operator<< Est appelé de manière récursive pour MyClass au lieu d'appeler operator<< Pour std::string.

Cela signifie-t-il que la chaîne #include <iostream> N'est incluse que partiellement?

Non, la chaîne est entièrement incluse, sinon vous ne pourriez pas l'utiliser.

162
Pavel

Le problème est que votre code effectue une récursion infinie. L’opérateur de diffusion en continu de std::string (std::ostream& operator<<(std::ostream&, const std::string&)) est déclaré dans le fichier d’en-tête <string>, Bien que std::string Soit lui-même déclaré dans un autre fichier d’en-tête (inclus par les deux <iostream> Et <string>).

Lorsque vous n'incluez pas <string>, Le compilateur essaie de trouver un moyen de compiler ausgabe << f.getName();.

Il arrive que vous ayez défini à la fois un opérateur de streaming pour MyClass et un constructeur qui admet un std::string, De sorte que le compilateur l’utilise (via construction implicite ), créant ainsi un appel récursif.

Si vous déclarez explicit votre constructeur (explicit MyClass(const std::string& s)), votre code ne sera plus compilé, car il n'y a aucun moyen d'appeler l'opérateur de streaming avec std::string, Et vous ' ll sera obligé d'inclure l'en-tête <string>.

[~ # ~] éditer [~ # ~]

Mon environnement de test est VS 2010 et, à partir du niveau d’avertissement 1 (/W1), Il vous avertit du problème:

avertissement C4717: 'opérateur <<': récursif sur tous les chemins de contrôle, la fonction provoquera un dépassement de capacité de la pile au moment de l'exécution

35
cbuchart