为什么 # 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;

但是当我运行我的程序时,我得到一个异常错误:

在 OperatorString.exe: 0xC00000FD: 栈溢出(参数: 0x0000001,0x01202FC4)中的0x77846B6E (ntdll.dll)

知道为什么在注释 #include <string>时会出现运行时错误吗? 我使用的是 VS2013Express。

8085 次浏览

确实,非常有趣的行为。

Any idea why I get I runtime error when commenting out #include <string>

使用 MSVC + + 编译器会出现错误,因为如果没有 #include <string>,就不会为 std::string定义 operator<<

当编译器尝试编译 ausgabe << f.getName();时,它寻找为 std::string定义的 operator<<。因为它没有被定义,编译器寻找替代方案。有一个为 MyClass定义的 operator<<,编译器尝试使用它,使用它必须将 std::string转换为 MyClass,这正是发生了什么,因为 MyClass有一个非显式的构造函数!因此,编译器最终将创建 MyClass的一个新实例,并尝试将其再次流到输出流。这导致了无穷无尽的递归:

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

To avoid the error you need to #include <string> to make sure that there is an operator<< defined for std::string. Also you should make your MyClass constructor explicit to avoid this kind of unexpected conversion. 智慧法则: 如果构造函数只使用一个参数来避免隐式转换,那么就使构造函数显式化:

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


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

看起来 std::stringoperator<<只有在包含 <string>的情况下才会被定义(使用 MS 编译器) ,因此所有的编译器都会编译,但是你会得到一些意想不到的行为,因为 MyClass会递归地调用 operator<<,而不是 std::string调用 operator<<

这是否意味着通过 #include <iostream>字符串只包括一部分?

不,字符串是完全包含的,否则您将无法使用它。

问题是您的代码正在执行无限递归。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>头。

剪辑

我的测试环境是 VS2010,从警告级别1(/W1)开始,它会警告您有关问题:

警告 C4717:’运算符 < <’: 在所有控制路径上递归,函数将导致运行时堆栈溢出