Mengapa #include <string> mencegah kesalahan stack overflow di sini?

121

Ini adalah kode contoh saya:

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

Jika saya berkomentar, #include <string>saya tidak mendapatkan kesalahan kompiler, saya kira karena itu semacam dimasukkan melalui #include <iostream>. Jika saya "mengklik kanan -> Go to Definition" di Microsoft VS, keduanya mengarah ke baris yang sama di xstringfile:

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

Tetapi ketika saya menjalankan program saya, saya mendapatkan kesalahan pengecualian:

0x77846B6E (ntdll.dll) di OperatorString.exe: 0xC00000FD: Stack overflow (Parameter: 0x00000001, 0x01202FC4)

Adakah ide mengapa saya mendapatkan error runtime saat berkomentar #include <string>? Saya menggunakan VS 2013 Express.

di udara
sumber
4
Dengan rahmat tuhan. bekerja sempurna pada gcc, lihat ideone.com/YCf4OI
V78
apakah Anda mencoba visual studio dengan visual c ++ dan mengomentari termasuk <string>?
mengudara
1
@cbuchart: Meskipun pertanyaannya sudah terjawab, saya pikir ini adalah topik yang cukup kompleks sehingga memiliki jawaban kedua dengan kata-kata yang berbeda sangat berharga. Saya telah memilih untuk membatalkan penghapusan jawaban bagus Anda.
Balapan Lightness di Orbit
5
@Ruslan: Efektif, mereka. Artinya, #include<iostream>dan <string>mungkin keduanya termasuk <common/stringimpl.h>.
MSalters
3
Dalam Visual Studio 2015, Anda mendapatkan peringatan ...\main.cpp(23) : warning C4717: 'operator<<': recursive on all control paths, function will cause runtime stack overflowdengan menjalankan baris inicl /EHsc main.cpp /Fetest.exe
CroCo

Jawaban:

161

Sungguh, perilaku yang sangat menarik.

Tahu mengapa saya mendapatkan saya runtime error saat berkomentar #include <string>

Dengan kompiler MS VC ++ kesalahan terjadi karena jika Anda tidak melakukannya, #include <string>Anda tidak akan operator<<menentukan untukstd::string .

Ketika kompilator mencoba untuk mengkompilasi, ausgabe << f.getName();ia mencari yang operator<<didefinisikan untuk std::string. Karena tidak ditentukan, kompilator mencari alternatif. Ada yang operator<<didefinisikan untuk MyClassdan kompilator mencoba menggunakannya, dan untuk menggunakannya ia harus mengonversi std::stringke MyClassdan inilah yang terjadi karena MyClassmemiliki konstruktor non-eksplisit! Jadi, kompilator akhirnya membuat instance baru dari Anda MyClassdan mencoba mengalirkannya lagi ke aliran keluaran Anda. Ini menghasilkan rekursi tanpa akhir:

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

Untuk menghindari kesalahan, Anda perlu #include <string>memastikan bahwa ada yang operator<<ditentukan untuk std::string. Anda juga harus membuat MyClasskonstruktor Anda eksplisit untuk menghindari jenis konversi tak terduga ini. Aturan kebijaksanaan: buat konstruktor eksplisit jika mereka hanya mengambil satu argumen untuk menghindari konversi implisit:

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

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

Sepertinya operator<<untuk std::stringmendapat didefinisikan hanya ketika <string>disertakan (dengan compiler MS) dan untuk alasan itu semuanya mengkompilasi, namun Anda mendapatkan perilaku agak tak terduga seperti operator<<semakin disebut rekursif untuk MyClassbukan memanggil operator<<untuk std::string.

Apakah itu berarti bahwa melalui #include <iostream>string hanya dimasukkan sebagian?

Tidak, string disertakan sepenuhnya, jika tidak, Anda tidak akan dapat menggunakannya.

Pavel P
sumber
19
@airborne - Ini bukan "masalah khusus Visual C ++", tetapi apa yang dapat terjadi jika Anda tidak menyertakan header yang benar. Ketika menggunakan std::stringtanpa #include<string>segala macam hal dapat terjadi, tidak terbatas pada kesalahan waktu kompilasi. Memanggil fungsi atau operator yang salah tampaknya merupakan pilihan lain.
Bo Persson
15
Nah, ini bukan "memanggil fungsi atau operator yang salah"; kompilator melakukan persis seperti yang Anda perintahkan. Anda hanya tidak tahu Anda menyuruhnya melakukan ini;)
Lightness Races di Orbit
18
Menggunakan tipe tanpa menyertakan file header yang sesuai adalah bug. Titik. Mungkinkah penerapannya membuat bug lebih mudah dikenali? Tentu. Tapi itu bukan "masalah" dengan implementasinya, ini masalah dengan kode yang Anda tulis.
Cody Gray
4
Pustaka standar bebas menyertakan token yang ditentukan di tempat lain di std di dalamnya, dan tidak diharuskan untuk menyertakan seluruh header jika mereka mendefinisikan satu token.
Yakk - Adam Nevraumont
5
Agak lucu melihat sekelompok programmer C ++ berpendapat bahwa compiler dan / atau pustaka standar harus melakukan lebih banyak pekerjaan untuk membantu mereka. Di sini implementasinya sudah sesuai dengan haknya, sesuai standar, seperti yang sudah berkali-kali dikemukakan. Bisakah "tipu daya" digunakan untuk membuat ini lebih jelas bagi programmer? Tentu, tapi kita juga bisa menulis kode di Java dan menghindari masalah ini sama sekali. Mengapa MSVC harus membuat pembantu internalnya terlihat? Mengapa header harus menarik banyak dependensi yang sebenarnya tidak dibutuhkan? Itu melanggar seluruh semangat bahasa!
Cody Grey
35

Masalahnya adalah kode Anda melakukan rekursi tak terbatas. Operator streaming untuk std::string( std::ostream& operator<<(std::ostream&, const std::string&)) dideklarasikan dalam <string>file header, meskipun std::stringitu sendiri dideklarasikan di file header lain (disertakan oleh <iostream>dan <string>).

Ketika Anda tidak menyertakan <string>kompilator mencoba menemukan cara untuk mengkompilasi ausgabe << f.getName();.

Kebetulan Anda telah menentukan operator streaming untuk MyClassdan konstruktor yang mengakui a std::string, sehingga compiler menggunakannya (melalui konstruksi implisit ), membuat panggilan rekursif.

Jika Anda mendeklarasikan explicitkonstruktor ( explicit MyClass(const std::string& s)) maka kode Anda tidak akan dikompilasi lagi, karena tidak ada cara untuk memanggil operator streaming dengan std::string, dan Anda akan dipaksa untuk menyertakan <string>header.

EDIT

Lingkungan pengujian saya adalah VS 2010, dan mulai dari tingkat peringatan 1 ( /W1) ini memperingatkan Anda tentang masalah:

peringatan C4717: 'operator <<': rekursif di semua jalur kontrol, fungsi akan menyebabkan runtime stack overflow

cbuchart.dll
sumber