为什么std :: fstream类不使用std :: string?

36

这并不是一个设计问题,尽管它可能看起来像是。 (好吧,好吧,这有点像一个设计问题)。 我想知道的是为什么C ++ std :: fstream类在其构造函数或打开方法中不使用std :: string。 每个人都喜欢代码示例,所以:

#include <iostream>
#include <fstream>
#include <string>

int main()
{
    std::string filename = "testfile";      
    std::ifstream fin;

    fin.open(filename.c_str()); // Works just fine.
    fin.close();

    //fin.open(filename); // Error: no such method.
    //fin.close();
}

在处理文件时,这种情况总是让我感到困扰。C++ 库肯定会尽可能使用 std::string,不是吗?

10个回答

26

在C++03中,通过使用C字符串,std::fstream类减少了对std::string类的依赖。然而在C++11中,std::fstream类允许将std::string作为构造函数参数传递。

现在,你可能会想为什么没有从std::string到C字符串的透明转换,这样期望一个C字符串的类就可以像期望一个std::string的类那样接受std::string

原因在于这将导致一个循环转换,从而可能引起问题。例如,假设std::string可以转换为C字符串,这样你就可以使用std::stringfstream一起使用。再假设C字符串可以转换为std::string,如当前标准所述。现在考虑以下情况:

void f(std::string str1, std::string str2);
void f(char* cstr1, char* cstr2);

void g()
{
    char* cstr = "abc";
    std::string str = "def";
    f(cstr, str);  // ERROR:  ambiguous
}

由于您可以在std :: string和C字符串之间进行双向转换,因此对f()的调用可能会解析为两个f()替代方案之一,因此是模棱两可的。解决方法是通过使一个转换方向明确来打破转换循环,这就是STL选择使用c_str()的方式。


1
样例不应该与重载解析规则产生歧义。f(char*,std::string)是一个精确匹配,而其他的则需要转换,因此第一个函数将是最佳可行函数。如果您删除了第一个f,那么由于"(char*&,std::string&) -> (char*,std::string) -> (char*,char*)"是一个更好的转换序列,而不是"(char*&,std::string&) -> (char*,std::string) -> (std::string,char*)",第二个f将成为最佳可行函数。我有什么遗漏吗? - outis
2
这种微观优化真的很愚蠢,因为(1)字符串与语言的任何结构都紧密耦合,特别是与字符串相关的结构(这类似于说,我不希望GMP依赖于shorts),而且(2)char*是有害的,如果用户没有必要使用它,你不应该鼓励他们使用它,编译时间中的毫秒值完全值得,最后(3)他们本可以定义<stringfwd>并在不依赖它的情况下使用字符串,所以我认为,在这种情况下,解耦依赖关系根本不是一个有效的理由。 - Elazar Leibovich
使用c_str()进行显式转换而不是使用转换构造函数进行隐式类型转换的原因是,C字符串需要额外的\0,因此std::string无法返回指向其内部表示的const char*指针,这将导致性能下降。使用显式转换,程序员可以更好地决定。 - Micha Wiedenmann

14

在C++标准委员会中,有一些地方没有真正优化标准库中设施之间的交互。其中一个例子就是 std::string 及其在库中的使用。

另一个例子是 std::swap。许多容器都有一个交换成员函数,但没有提供 std::swap 的重载。std::sort 也是如此。

我希望在即将推出的标准中,所有这些小问题都能得到解决。


11

或许这可以让你稍微安心一些:在C++0x标准的草案中,所有的fstream都会有open(string const &, ...)接口,与open(char const *, ...)并存。(例如可以参考27.8.1.6中basic_ifstream的声明)

因此,当它最终确定并实现时,它将不再使你感到困扰了 :)


9

在STL之前,流IO库已经被添加到标准的C++库中。为了不破坏向后兼容性,决定在添加STL时避免修改IO库,即使这意味着会出现像你提出的那样的问题。


3

@ Bernard:
单体应用程序的“解耦”。“一人得道,鸡犬升天”可能适用于火枪手,但对于类设计师来说并不是那么有效。以下是一个不太典型的例子,它说明了当设计变成过度设计时会出现多大的错误。不幸的是,这个例子来自你附近的标准库... ~ http://www.gotw.ca/gotw/084.htm


2
这是微不足道的,没错。在这里,std::string接口过于庞大指的是什么?在这个上下文中,“庞大”是指方法调用很多吗?我并不是在挑刺,我真的很感兴趣。
它拥有比实际需要更多的方法,并且使用整数偏移量而不是迭代器的行为有点可疑(因为它与库的其余部分工作方式相反)。
我认为真正的问题是C++库有三个部分:它有旧的C库,它有STL,还有字符串和流。虽然一些努力已经被做出来以桥接不同的部分(例如添加对C库的重载,因为C++支持重载;向basic_string添加迭代器;添加iostream迭代器适配器),但是当你看到细节时,会发现存在很多不一致性。
例如,basic_string包括一些不必要的标准算法副本方法;各种查找方法可能可以安全地删除。另一个例子:本地化使用原始指针而不是迭代器。

1

C++的诞生是在比今天我们写代码的大型机器更小的机器上。当iostream出现时,许多开发人员真正关心的是代码大小(他们必须将整个程序和数据放入几百KB中)。因此,许多人不想引入“大”C++字符串库。出于同样的原因,许多人甚至没有使用iostream库,即代码大小。

我们没有像今天这样拥有数千兆字节的RAM可以随意使用。我们通常没有函数级链接,因此我们受到库开发人员的支配,要么使用许多单独的对象文件,要么引入大量未调用的代码。所有这些FUD都使开发人员远离std::string。

那时候我也避免使用std::string。 "太臃肿","过于频繁地调用malloc"等等。愚蠢地为字符串使用基于堆栈的缓冲区,然后添加各种乏味的代码以确保它不会溢出。


0

现在你可以很容易地解决这个问题:将-std=c++11添加到你的CFLAGS中。


0

STL 中是否有任何类可以接受字符串作为参数?我认为没有(在快速搜索中找不到)。这可能是一种设计决策,即 STL 中的任何类都不应该依赖于其他 STL 类(除了直接需要的功能)。


0

我相信这已经被考虑过并且是为了避免依赖而做的;即 #include <fstream> 不应该强制要求 #include <string>。

说实话,这似乎是一个不太重要的问题。更好的问题是,为什么 std::string 的接口如此之大?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接