Чому #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.