为什么#include <string>会阻止堆栈溢出错误?

air*_*rne 121 c++ stack-overflow string explicit

这是我的示例代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

如果我注释掉,#include <string>我没有得到任何编译器错误,我想因为它包含在内#include <iostream>.如果我在Microsoft VS中"右键单击 - >转到定义",它们都指向xstring文件中的同一行:

typedef basic_string<char, char_traits<char>, allocator<char> >
    string;
Run Code Online (Sandbox Code Playgroud)

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

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

知道为什么我在评论时出现运行时错误#include <string>?我正在使用VS 2013 Express.

Pav*_*l P 161

确实,非常有趣的行为.

知道为什么我在评论时出现运行时错误 #include <string>

使用MS VC++编译器时会发生错误,因为如果不这样做#include <string>,则无法operator<<定义std::string.

当编译器尝试编译时,ausgabe << f.getName();它会查找已operator<<定义的for std::string.由于未定义,编译器会寻找替代方案.有一个operator<<定义的MyClass,编译器尝试使用它,并使用它必须转换std::stringMyClass,这正是发生的事情,因为MyClass有一个非显式的构造函数!因此,编译器最终会创建一个新的实例,MyClass并尝试将其再次流式传输到输出流.这导致无休止的递归:

 start:
     operator<<(MyClass) -> 
         MyClass::MyClass(MyClass::getName()) -> 
             operator<<(MyClass) -> ... goto start;
Run Code Online (Sandbox Code Playgroud)

为了避免错误,您需要#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;
    }
};
Run Code Online (Sandbox Code Playgroud)

它看起来像operator<<std::string时候被定义只<string>包括(与MS的编译器),因为这个原因,一切编译,但是你得到的多少有些意外的行为operator<<是越来越递归调用的MyClass,而不是调用operator<<std::string.

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

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

  • @airborne - 它不是"Visual C++特定问题",但是当你没有包含正确的标题时会发生什么.当使用没有`#include <string>的`std :: string`时,可能会发生各种各样的事情,不仅限于编译时错误.调用错误的函数或运算符显然是另一种选择. (19认同)
  • 使用不包含其相应头文件的类型是一个错误.期.实施是否可以更容易发现错误?当然.但这与实现不是*"问题"*,这是您编写的代码的问题. (18认同)
  • 嗯,这不是"调用错误的函数或操作符"; 编译器正在完成你告诉它要做的事情.你只是不知道你是在告诉它这样做;) (15认同)
  • 看到一群C++程序员认为编译器和/或标准库应该做更多的工作来帮助他们,这有点幽默.正如已经多次指出的那样,根据标准,这里的实施完全在其权利范围内.可以使用"欺骗"来使程序员更明显吗?当然,但我们也可以用Java编写代码并完全避免这个问题.为什么MSVC应该让内部助手可见?为什么标题会拖入一堆实际上并不需要的依赖项?这违反了语言的整体精神! (5认同)
  • 标准库可以自由地包含在std中其他地方定义的标记,如果它们定义了一个标记,则不需要包含整个标头. (4认同)
  • 问题不在于`<iostream>`使`std :: string`可见(如果我们是技术性的,可以看作标准的间接_required_,因为`istream`和`ostream`间接得出来自`ios_base`,它定义了一个成员类型,`ios_base :: failure`,它有一个带有`const string&`的构造函数(在[`[ios :: failure]`]中指定(https:// timsong- cpp.github.io/cppwp/n4140/ios::failure))),但是在MSVC中也没有让相关帮助者可见(虽然他们不允许这样做,但他们真的不应该这样做). (4认同)
  • @BoPersson我称之为VC++问题,因为如果不包含`<string>`,他们应该更加小心确保std :: string不存在.基本上,他们可以在内部包含他们想要的任何内容,但实际的字符串头可以将basic_string和所有typedef注入std命名空间(并且没有它std :: string将无法使用). (3认同)
  • @BoPersson相反,我会说"通过提供`std :: string`来让缺乏经验的程序员陷入虚假的安全感,但是`<string>`中指定的帮助程序都不是MSVC特有的问题,因为当包含`<iostream>`时,大多数编译器会提供所有`<string>`或隐藏`std :: string`. (3认同)
  • @xaxxon很可能你不会看到它,因为ctor会完成,你只需得到看起来好像`operator <<`无休止地调用自己的stacktrace. (2认同)
  • 故障最终是由用户引起的,但由于MS VC++存在错误.他们_可以留下半定义的东西并不意味着他们_should_. (2认同)

cbu*_*art 35

问题是你的代码正在进行无限递归.对于流媒体运营商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:'operator <<':在所有控制路径上递归,函数将导致运行时堆栈溢出