12

Is it possible to use an std::string for read() ?

Example :

std::string data;

read(fd, data, 42);

Normaly, we have to use char* but is it possible to directly use a std::string ? (I prefer don't create a char* for store the result)

Thank's

iammilind
  • 68,093
  • 33
  • 169
  • 336
Zat42
  • 2,471
  • 4
  • 22
  • 36

6 Answers6

14

Well, you'll need to create a char* somehow, since that's what the function requires. (BTW: you are talking about the Posix function read, aren't you, and not std::istream::read?) The problem isn't the char*, it's what the char* points to (which I suspect is what you actually meant).

The simplest and usual solution here would be to use a local array:

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);
}

If you want to capture directly into an std::string, however, this is possible (although not necessarily a good idea):

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

This avoids the copy, but quite frankly... The copy is insignificant compared to the time necessary for the actual read and for the allocation of the memory in the string. This also has the (probably negligible) disadvantage of the resulting string having an actual buffer of 42 bytes (rounded up to whatever), rather than just the minimum necessary for the characters actually read.

(And since people sometimes raise the issue, with regards to the contiguity of the memory in std:;string: this was an issue ten or more years ago. The original specifications for std::string were designed expressedly to allow non-contiguous implementations, along the lines of the then popular rope class. In practice, no implementor found this to be useful, and people did start assuming contiguity. At which point, the standards committee decided to align the standard with existing practice, and require contiguity. So... no implementation has ever not been contiguous, and no future implementation will forego contiguity, given the requirements in C++11.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • "This avoids the copy, but quite frankly... The copy is insignificant compared to the time necessary for the actual read and for the allocation of the memory in the string." It depends on the size of the data being read. People invested quite a lot of effort into making the Linux kernel zero-copy on several data-transfer paths. – user1202136 Apr 11 '12 at 13:08
  • @user1202136 In this case, the size of the copy is 42 bytes. And there is a dynamic allocation, and a transfer of bytes from the system. The situation is different in the kernel, at least when dealing with things like buffers (which are likely on the order of 4KB or more, and are allocated from pools). – James Kanze Apr 11 '12 at 13:45
  • 1
    Since C++11, https://en.cppreference.com/w/cpp/string/basic_string/operator_at, using `&str[0]` is explicitly UB: *For the first (non-const) version, the behavior is undefined if this character is modified to any value other than `CharT()`*. And that's just for modifying that single character. However, C++17 does add a non-const overload of [`.data()`](https://en.cppreference.com/w/cpp/string/basic_string/data) which you could use after `.resize()`. In practice you should be fine to use `&str[0]`, especially in C++11 and later where it's guaranteed that `&str[i] = str.data()+i` – Peter Cordes Mar 28 '20 at 16:36
  • @user1202136 what does Linux kernel have to do with this? The copy happens from the intermediate buffer that you yourself allocated, to the string. I've spent few hours today, trying to benchmark the `std::string::resize` with direct read and buffered read using `std::string::append` with buffer contents, and so far I found no answer which is better, but my flamegraph fluctuates on 1% difference between the two, and ironically enough, that margin is in favor of buffer + append just a bit more often with 8KiB buffer... Few MiB buffer is ~5% favor for resize, but what kind of IO is that fast? –  Mar 11 '22 at 15:43
  • @Sahsahae The Linux kernel is not directly related to the answer. Rather, I brought it as evidence that copying buffers can be expensive, and that engineers are willing to invest significant effort in avoiding needless copying. AFAIU, memory bandwidth becomes a bottleneck when you want to saturate the network card. A back-of-the-envelope calculation would look as follows: DDR5 tops 50GB/s, whereas network cards top ~10GB/s. So you can copy data at most 5 times. Notice that 50GB/s is likely in "burst mode", so in practice you get way less memory bandwidth than that. Anyway, copying matters. :) – user1202136 Mar 15 '22 at 11:33
1

No, you cannot and you should not. Usually, std::string implementations internally store other information such as the size of the allocated memory and the length of the actual string. C++ documentation explicitly states that modifying values returned by c_str() or data() results in undefined behaviour.

user1202136
  • 11,171
  • 4
  • 41
  • 62
0

If the read function requires a char *, then no. You could use the address of the first element of a std::vector of char as long as it's been resized first. I don't think old (pre C++11) strings are guarenteed to have contiguous memory otherwise you could do something similar with the string.

Anon Mail
  • 4,660
  • 1
  • 18
  • 21
0

No, but

std::string data;
cin >> data;

works just fine. If you really want the behaviour of read(2), then you need to allocate and manage your own buffer of chars.

DRVic
  • 2,481
  • 1
  • 15
  • 22
0

Because read() is intended for raw data input, std::string is actually a bad choice, because std::string handles text. std::vector seems like the right choice to handle raw data.

stefaanv
  • 14,072
  • 2
  • 31
  • 53
-1

Using std::getline from the strings library - see cplusplus.com - can read from an stream and write directly into a string object. Example (again ripped from cplusplus.com - 1st hit on google for getline):

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

So will work when reading from stdin (cin) and from a file (ifstream).

Gary Robinson
  • 331
  • 2
  • 6