Почему #include <string> предотвращает ошибку здесь?
Это мой пример кода:
#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.
Ответы
Ответ 1
Действительно, очень интересное поведение.
Любая идея, почему я получаю ошибку времени выполнения при комментировании #include <string>
С компилятором MS VС++ ошибка возникает, потому что если вы не #include <string>
, у вас не будет operator<<
, определенного для std::string
.
Когда компилятор пытается скомпилировать ausgabe << f.getName();
, он ищет operator<<
, определенный для std::string
. Поскольку он не был определен, компилятор ищет альтернативы. Для MyClass
существует operator<<
, и компилятор пытается его использовать, и для его использования он должен преобразовать std::string
в MyClass
, и это именно то, что происходит, потому что MyClass
имеет неявный конструктор! Итак, компилятор заканчивает создание нового экземпляра вашего MyClass
и пытается передать его снова в выходной поток. Это приводит к бесконечной рекурсии:
start:
operator<<(MyClass) ->
MyClass::MyClass(MyClass::getName()) ->
operator<<(MyClass) -> ... goto start;
Чтобы избежать ошибки, вам нужно #include <string>
убедиться, что для std::string
существует operator<<
. Также вы должны сделать свой конструктор 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>
включена только частично?
Нет, строка полностью включена, иначе вы не сможете ее использовать.
Ответ 2
Проблема заключается в том, что ваш код выполняет бесконечную рекурсию. Оператор потоковой передачи для std::string
(std::ostream& operator<<(std::ostream&, const std::string&)
) объявлен в заголовочном файле <string>
, хотя сам std::string
объявлен в другом файле заголовка (включенном как <iostream>
, так и <string>
).
Когда вы не включаете <string>
, компилятор пытается найти способ скомпилировать ausgabe << f.getName();
.
Бывает, что вы определили как оператор потоковой передачи для MyClass
, так и конструктор, который допускает std::string
, поэтому компилятор использует его (через неявное построение), создавая рекурсивный вызов.
Если вы объявите explicit
ваш конструктор (explicit MyClass(const std::string& s)
), ваш код больше не будет компилироваться, так как нет способа вызвать оператора потоковой передачи с std::string
, и вы будете вынуждены включить <string>
.
ИЗМЕНИТЬ
Моя тестовая среда - VS 2010, и начиная с уровня предупреждения 1 (/W1
) он предупреждает вас о проблеме:
предупреждение C4717: 'оператор < <: рекурсивный на всех путях управления, функция вызовет переполнение стека выполнения