std::string.resize() and std::string.length()

后端 未结 7 950
闹比i
闹比i 2020-12-11 23:51

I\'m relatively new to C++ and I\'m still getting to grips with the C++ Standard Library. To help transition from C, I want to format a std::string using printf

相关标签:
7条回答
  • 2020-12-11 23:58

    I think that there are no guarantees that the layout of the string as referenced by &output[0] is contiguous and that you can write to it.

    Use std::vector instead as a buffer which is guaranteed to have contiguous storage since C++03.

    using namespace std;
    
    string formatStdString(const string &format, ...)
    {
        va_list va;
        vector<string::value_type> output(1); // ensure some storage is allocated
        size_t needed;
        size_t used;
    
        va_start(va, format);
        needed = vsnprintf(&output[0], 0, format.c_str(), va);
        output.resize(needed); // don't need null terminator
        va_end(va);    
    
        // Here we should ensure that needed != 0
        va_start(va, format);
        used = vsnprintf(&output[0], output.size(), format.c_str(), va); // use size()
        // assert(used == needed);
        va_end(va);
    
        return string(output.begin(), output.end());
    }
    

    NOTE: You'll have to set an initial size to the vector as the statement &output[0] can otherwise attempt to reference a non-existing item (as the internal buffer might not have been allocated yet).

    0 讨论(0)
  • 2020-12-12 00:05

    1) You do not need to make space for the null terminator.
    2) capacity() tells you how much space the string has reserved internally. length() tells you the length of the string. You probably don't want capacity()

    0 讨论(0)
  • 2020-12-12 00:14

    With the current standard (the upcomming standard differs here) there is no guarantee that the internal memory buffer managed by the std::string will be contiguous, or that the .c_str() method returns a pointer to the internal data representation (the implementation is allowed to generate a contiguous read-only block for that operation and return a pointer into it. A pointer to the actual internal data can be retrieved with the .data() member method, but note that it also returns a constant pointer: i.e. it is not intended for you to modify the contents. The buffer return by .data() it is not necessarily null terminated, the implementation only needs to guarantee the null termination when c_str() is called, so even in implementations where .data() and .c_str() are called, the implementation can add the \0 to the end of the buffer when the latter is called.

    The standard intended to allow rope implementations, so in principle it is unsafe to do what you are trying, and from the point of view of the standard you should use an intermediate std::vector (guaranteed contiguity, and there is a guarantee that &myvector[0] is a pointer to the first allocated block of the real buffer).

    In all implementations I know of, the internal memory handled by std::string is actually a contiguous buffer and using .data() is undefined behavior (writting to a constant variable) but even if incorrect it might work (I would avoid it). You should use other libraries that are designed for this purpose, like boost::format.

    About the null termination. If you finally decide to follow the path of the undefined... you would need to allocate extra space for the null terminator, since the library will write it into the buffer. Now, the problem is that unlike C-style strings, std::strings can hold null pointers internally, so you will have to resize the string down to fit the largest contiguous block of memory from the beginning that contains no \0. That is probably the issue you are finding with spurious null characters. This means that the bad approach of using vsnprintf(or the family) has to be followed by str.resize( strlen( str.c_str() ) ) to discard all contents of the string after the first \0.

    Overall, I would advice against this approach, and insist in either getting used to the C++ way of formatting, using third party libraries (boost is third party, but it is also the most standard non-standard library), using vectors or managing memory like in C... but that last option should be avoided like the plague.

    // A safe way in C++ of using vsnprintf:
    std::vector<char> tmp( 1000 ); // expected maximum size
    vsnprintf( &tmp[0], tmp.size(), "Hi %s", name.c_str() ); // assuming name to be a string
    std::string salute( &tmp[0] );
    
    0 讨论(0)
  • 2020-12-12 00:14

    My implementation for variable argument lists for functions is like this:

    std::string format(const char *fmt, ...)
    {
      using std::string;
      using std::vector;
    
      string retStr("");
    
      if (NULL != fmt)
      {
         va_list marker = NULL;
    
         // initialize variable arguments
         va_start(marker, fmt);
    
         // Get formatted string length adding one for NULL
         size_t len = _vscprintf(fmt, marker) + 1;
    
         // Create a char vector to hold the formatted string.
         vector<char> buffer(len, '\0');
         int nWritten = _vsnprintf_s(&buffer[0], buffer.size(), len, fmt,
    marker);
    
         if (nWritten > 0)
         {
            retStr = &buffer[0];
         }
    
         // Reset variable arguments
         va_end(marker);
      }
    
      return retStr;
    }
    
    0 讨论(0)
  • 2020-12-12 00:18

    The std::string class takes care of the null terminator for you.

    However, as pointed out, since you're using vnsprintf to the raw underying string buffer (C anachronisms die hard...), you will have to ensure there is room for the null terminator.

    0 讨论(0)
  • 2020-12-12 00:19

    Use boost::format, if you prefer printf() over streams.

    Edit: Just to make this clear, actually I fully agree with Alan, who said you should use streams.

    0 讨论(0)
提交回复
热议问题