【问题标题】:Init std::string with single copy用单个副本初始化 std::string
【发布时间】:2015-10-29 07:45:29
【问题描述】:

我在 Win32 上的 C++ 中有以下代码。它只是一些 Win32 API 上的 C++ 扭曲,返回 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());
}

这段代码让我烦恼的是结果的双重副本。

  1. 通过API转换成WCHARs的向量。
  2. 从向量变成std::wstring

有没有一种方法可以只用一个副本来实现此代码,而无需对函数的签名进行重大更改。 这是一个具体的例子,但我对通用解决方案和使用std::wstring/std::string 的正确方法更感兴趣,因为这种模式在代码中的很多地方都有体现。

【问题讨论】:

    标签: c++ c++11 stdstring


    【解决方案1】:

    关于 C++ 方面,您可以直接使用 wstring 作为结果变量。

    要获得指向非零大小的wstring 缓冲区的指针,只需使用&amp;s[0]

    就像std::vector 一样,std::basic_string 具有保证的连续缓冲区。

    对于return,它可能会得到Return Value Optimization (RVO),如果没有,它将被移动。

    免责声明:我没有查看 API 函数的文档。我不知道这段代码是否正确甚至有意义。我只是假设。

    【讨论】:

    • @Angew:不客气。在我发现遗漏之前,我也很确定。这很奇怪。
    • 我通常更喜欢front 而不是&amp;s[0]。它甚至适用于 list 这样的容器。
    • 但是为什么你想要一个指向list 中第一个节点的指针呢?
    • 在通用代码的某个地方你可能需要它,可能是因为你没有正常的开始/结束范围。
    【解决方案2】:
    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;
    }
    

    编辑:

    回想一下,既然我们使用的是 c++,让我们全力以赴,进行适当的错误检测并使用信息丰富的嵌套异常链报告错误:

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

    【讨论】:

    • 谨慎的做法是检查 0 大小。表达式 &amp;s[0] 是该大小的 UB。
    • @Cheersandhth.-Alf 重新考虑了错误处理。在我看来,最好将 os 错误转换为 system_error。
    • 通过适当地定义函数fail,您可以将其写为ExpandEnvironmentStrings( ... ) || fail( "message" )。它在 C++ 中的使用并不多,但在脚本语言中却是惯用的。我喜欢它。
    • 同意。这实际上是我在自己的项目中所做的。如果使用关键字“or”代替“||”,效果会更好
    • 根据 cppreference.com。 “写入通过 c_str() 访问的字符数组是未定义的行为。”我知道这可能没问题,但这不是标准的一部分。
    猜你喜欢
    • 1970-01-01
    • 2016-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-12
    相关资源
    最近更新 更多