能否使用std::string来进行read()操作?

12

是否可以使用std :: string来进行read()操作?

示例:

std::string data;

read(fd, data, 42);

通常情况下,我们需要使用char*,但是是否可以直接使用std::string?(我不想为存储结果创建一个char*)

谢谢


如果零拷贝是您的目标,将“read”替换为“mmap”,然后在其周围包装一个固定分配的std :: string是否有帮助? - user1202136
您读取的字符串中是否有空格? - Peter Wood
6个回答

14

好的,因为这是函数要求的内容,所以你需要创建一个 char* 。 (顺便说一下:你是在谈论 Posix 函数 read,而不是 std::istream::read 吗?) 问题不在于 char* ,而在于 char* 指向的内容(我猜这才是你真正想表达的)。

这里最简单和常见的解决方案是使用一个本地数组:

char buffer[43];
int len = read(fd, buffer, 42);
if ( len < 0 ) {
    //  read error...
} else if ( len == 0 ) {
    //  eof...
} else {
    std::string data(buffer, len);
}

如果你想直接将内容捕获到 std::string 中,这是可能的(尽管不一定是一个好主意):

std::string data;
data.resize( 42 );
int len = read( fd, &data[0], data.size() );
//  error handling as above...
data.resize( len );  //  If no error...

这样可以避免拷贝,但坦白说...与实际读取和分配字符串内存所需的时间相比,拷贝是微不足道的。此外,这也有(可能微不足道的)缺点,即生成的字符串具有实际缓冲区大小为42字节(向上舍入到任何值),而不仅仅是实际读取的字符所需的最小值。

(由于有些人有时会提出问题,关于std::string中内存的连续性:这是十年或更久以前的问题。最初的std::string规范专门设计为允许非连续实现,沿用当时流行的rope类的方式进行。实际上,没有一个实现者发现这是有用的,人们开始假设连续性。在此之后,标准委员会决定将标准与现有实践对齐,并要求连续性。因此...没有任何实现不是连续的,也没有未来的实现会放弃连续性,考虑到C ++11中的要求。)


这样可以避免复制,但坦白地说...与实际读取和在字符串中分配内存所需的时间相比,复制是微不足道的。这取决于正在读取的数据大小。人们投入了相当多的精力,使Linux内核在几个数据传输路径上实现零拷贝。 - user1202136
@user1202136 在这种情况下,复制的大小为42字节。并且存在动态分配和从系统传输字节的操作。在内核中情况有所不同,至少在处理缓冲区等内容时(这些内容可能是4KB或更多,并且是从池中分配的)。 - James Kanze
1
自从C++11以来,https://en.cppreference.com/w/cpp/string/basic_string/operator_at,使用`&str [0]明确是UB:*对于第一个(非const)版本,如果修改此字符为任何值而不是CharT(),则行为未定义。*而且这仅适用于修改该单个字符。但是,C++17确实添加了[.data()](https://en.cppreference.com/w/cpp/string/basic_string/data)的非const重载,您可以在.resize()之后使用它。实际上,在C++11及更高版本中,保证&str[i] = str.data()+i,因此您应该可以放心使用&str [0]`。 - Peter Cordes
@user1202136 Linux内核与此有什么关系?复制是从您自己分配的中间缓冲区到字符串中进行的。我今天花了几个小时,尝试使用std::string::append和缓冲区内容直接读取以及缓冲读取来对std::string::resize进行基准测试,到目前为止,我没有找到更好的答案,但我的火焰图在两者之间波动1%,具有讽刺意味的是,在8KiB缓冲区中,这个差距略微更倾向于缓冲区+附加...几个MiB缓冲区是resize约5%的优势,但是哪种IO会那么快呢? - user11877195
@Sahsahae Linux内核与答案没有直接关系。相反,我将其作为证据,表明复制缓冲区可能是昂贵的,并且工程师愿意投入大量精力来避免不必要的复制。据我所知,当您想要饱和网络卡时,内存带宽会成为瓶颈。一个简单的估算如下:DDR5最高可达50GB/s,而网络卡最高可达约10GB/s。因此,您最多可以复制数据5次。请注意,50GB/s很可能是“突发模式”,因此实际上您获得的内存带宽要少得多。无论如何,复制都很重要。 :) - user1202136

1
不可以,也不应该这样做。通常情况下,std::string的实现内部存储其他信息,例如分配内存的大小和实际字符串的长度。C++文档明确指出,修改 c_str()或 data()返回的值会导致未定义的行为。

0
如果read函数需要一个char *,那么不行。你可以使用已经调整大小的std::vector的第一个元素的地址,只要它已经被调整过了。我认为旧的(C++11之前的)字符串不能保证具有连续的内存,否则你可以用类似的方式处理字符串。

0

不,但是

std::string data;
cin >> data;

这个程序运行得很好。如果你真的想要read(2)的行为,那么你需要分配和管理自己的字符缓冲区。


这将在空格处中断。 - Peter Wood

0

因为read()函数旨在进行原始数据输入,所以std::string实际上是一个不好的选择,因为std::string处理文本。std::vector似乎是处理原始数据的正确选择。


-1

使用字符串库中的std::getline - 参见cplusplus.com - 可以从流中读取并直接写入字符串对象。例如(再次从cplusplus.com中获取 - 在谷歌上第一次搜索getline):

int main () {
  string str;
  cout << "Please enter full name: ";
  getline (cin,str);
  cout << "Thank you, " << str << ".\n";
}

当从标准输入(stdin)和文件(ifstream)读取时,它将起作用。


为什么这很重要?read是posix上的系统调用,与控制台无关。 - Paulo Neves

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