Чому #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>. Якщо я "клацніть правою кнопкою миші -> Перейти до визначення" в Microsoft VS, вони обидва вказують на один і той же рядок у xstringфайлі:

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

Але коли я запускаю свою програму, я отримую помилку про виняток:

0x77846B6E (ntdll.dll) в OperatorString.exe: 0xC00000FD: Переповнення стека (Параметр: 0x00000001, 0x01202FC4)

Будь-яка ідея, чому я коментую помилку виконання #include <string>? Я використовую VS 2013 Express.


4
З Божою милістю. ідеально працює над gcc, дивіться ideone.com/YCf4OI
v78

ви спробували візуальну студію з візуальним c ++ і коментарі включають <string>?
ефірі

1
@cbuchart: Хоча на питання вже відповіли, я думаю, що це досить складна тема, що мати другу відповідь різними словами цінно. Я проголосував за те, щоб скасувати вашу чудову відповідь.
Гонки легкості по орбіті

5
@Ruslan: Ефективно, вони є. Тобто, може , #include<iostream>і те, і <string>інше <common/stringimpl.h>.
MSalters

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<<певний для MyClassі компілятор намагається використовувати його, і використовувати його він повинен перетворити std::stringв MyClassі це саме те , що відбувається , тому що MyClassмає не явний конструктор! Отже, компілятор в кінцевому підсумку створює новий екземпляр вашого MyClassі намагається передати його знову у вихідний потік. Це призводить до нескінченної рекурсії:

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

Щоб уникнути помилки, вам потрібно #include <string>переконатися, що вона operator<<визначена std::string. Крім того, ви повинні зробити свій MyClassконструктор явним, щоб уникнути подібного роду несподіваних перетворень. Правило мудрості: зробіть конструктори явними, якщо вони беруть лише один аргумент, щоб уникнути неявного перетворення:

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

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

Схоже , що operator<<для std::stringотримує певний тільки тоді , коли <string>включений (з компілятором MS) і з цієї причини все відкомпільоване, однак ви отримаєте декілька несподівану поведінку, operator<<стає викликається рекурсивно для MyClassзамість виклику operator<<для std::string.

Чи означає це, що через #include <iostream>рядок включається лише частково?

Ні, рядок повністю включений, інакше ви не зможете ним скористатися.


19
@airborne - Це не "специфічна проблема Visual C ++", але те, що може статися, якщо ви не включите належний заголовок. Якщо використовувати std::stringбез #include<string>всяких речей, це може статися, не обмежуючись помилкою компіляції у часі. Виклик неправильної функції або оператора, мабуть, інший варіант.
Бо Персон

15
Ну, це не "виклик неправильної функції або оператора"; компілятор робить саме те, що ви йому сказали. Ви просто не знали, що вам це говорять;)
Гонки легкості в Орбіті

18
Використання типу без включення відповідного файла заголовка - помилка. Період. Чи може реалізація спростила виявити помилку? Звичайно. Але це не "проблема" з реалізацією, це проблема з написаним вами кодом.
Коді Грей

4
Стандартні бібліотеки можуть безкоштовно включати маркери, визначені в іншому місці std всередині себе, і не потрібно включати весь заголовок, якщо вони визначають один маркер.
Якк - Адам Невраумон

5
Дещо жартівливо бачити купу програмістів на C ++, які стверджують, що компілятор та / або стандартна бібліотека повинні робити більше роботи, щоб їм допомогти. Здійснення цього закону є в межах своїх прав, відповідно до стандарту, про що неодноразово вказувалося. Чи можна використовувати "хитрість", щоб зробити це більш очевидним для програміста? Звичайно, але ми також могли написати код на Java і взагалі уникнути цієї проблеми. Чому MSVC повинен зробити своїх внутрішніх помічників видимими? Чому заголовок повинен перетягувати купу залежностей, які йому насправді не потрібні? Це порушує весь дух мови!
Коді Грей

35

Проблема полягає в тому, що ваш код робить нескінченну рекурсію. Оператор потокової передачі для std::string( std::ostream& operator<<(std::ostream&, const std::string&)) оголошується у <string>файлі заголовка, хоча std::stringсам оголошується в іншому файлі заголовка (включений обома <iostream>і <string>).

Якщо ви не включаєте <string>компілятор, він намагається знайти спосіб компіляції ausgabe << f.getName();.

Буває так, що ви визначили як оператор потокової передачі для, так MyClassі конструктор, який приймає a std::string, тому компілятор використовує його (через неявну побудову ), створюючи рекурсивний виклик.

Якщо ви оголосите explicitконструктора ( explicit MyClass(const std::string& s)), ваш код більше не буде компілюватися, оскільки немає можливості викликати потокового оператора std::string, і ви будете змушені включати <string>заголовок.

EDIT

Моє тестове середовище - VS 2010, і починаючи з рівня попередження 1 ( /W1), воно попереджає вас про проблему:

попередження C4717: "оператор <<": рекурсивна на всіх контурах управління, функція спричинить переповнення стеку виконання

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.