Pagefault
Pagefault

Reputation: 349

Init std::string with single copy

I have the following code in C++ on Win32. It's simply a C++ warp on some Win32 API that returns a CHAR *:

wstring expandEnvironmentVariables(const wstring & str)
{
    DWORD neededSize = ExpandEnvironmentStrings(str.c_str(), nullptr, 0);
    vector<WCHAR> expandedStr(neededSize);
    if (0 == ExpandEnvironmentStrings(str.c_str(), expandedStr.data(), static_cast<DWORD>(expandedStr.size()))) {
        return wstring(str);
    }
    return wstring(expandedStr.data());
}

What bothers me about this code, is the double copy of the result.

  1. by the API into a vector of WCHARs.
  2. from the vector into std::wstring.

Is there a way to implement this code with just a single copy, and without a major change to the signature of the function. This is a specific example, but I'm more interested in the general solution and the right way to work with std::wstring/std::string, because this pattern shows itself in many places in the code.

Upvotes: 3

Views: 392

Answers (2)

Richard Hodges
Richard Hodges

Reputation: 69892

wstring expandEnvironmentVariables(const wstring & str)
{
    wstring expandedStr;
    DWORD neededSize = ExpandEnvironmentStrings(str.c_str(), 
                                                nullptr, 0);
    if (neededSize) 
    {
      expandedStr.resize(neededSize);
      if (0 == ExpandEnvironmentStrings(str.c_str(), 
                                        &expandedStr[0], 
                                        neededSize)) 
      {
          // pathological case requires a copy
          expandedStr = str;
      }
    }
    // RVO here
    return expandedStr;
}

EDIT:

On reflection, since we're using c++ let's go the whole hog and put in proper error detection and report errors with an informative nested exception chain:

DWORD check_not_zero(DWORD retval, const char* context)
{
  if(!retval)
      throw std::system_error(GetLastError(),
                              std::system_category(),
                              context);
    return retval;
}

std::wstring expandEnvironmentVariables(const std::wstring & str)
try
{
    DWORD neededSize = check_not_zero(ExpandEnvironmentStrings(str.c_str(),
                                                               nullptr,
                                                               0),
                                      "ExpandEnvironmentStrings1");

    std::wstring expandedStr(neededSize, 0);
    check_not_zero(ExpandEnvironmentStrings(str.c_str(),
                                            &expandedStr[0],
                                            neededSize),
                   "ExpandEnvironmentStrings2");

    // RVO here
    return expandedStr;
}
catch(...)
{
    std::throw_with_nested(std::runtime_error("expandEnvironmentVariables() failed"));
}

Upvotes: 3

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145289

Regarding the C++ side you can just use a wstring directly as a result variable.

To get a pointer to the buffer of a wstring of non-zero size, just use &s[0].

Just like std::vector, std::basic_string has a guaranteed contiguous buffer.

For the return it will probably get Return Value Optimization (RVO), and if not then it will be moved.

Disclaimer: I haven't checked the documentation of the API functions. I do not know if this code correct or even meaningful. I'm just assuming that.

Upvotes: 3

Related Questions