记一则由临时对象引起的 bug

最近在用 imgui 写一个数据对比工具,里面需要使用 Win32 函数来选择文件路径。相关代码差不多是这个样子:

std::string GetOpenFilePath(const std::string &title) {
  char path[MAX_PATH];

  OPENFILENAME ofn;
  ofn.lpstrTitle = title.c_str();  // 设置对话框标题
  ofn.lpstrFile = path;
  ofn.nMaxFile = MAX_PATH;
  // ...

  if (!GetOpenFileNameA(&ofn)) {
    // error
    return {}; 
  }

  return path;
}

由于在程序中使用了 utf8 编码,而 Win32 需要 GBK 编码,故需要处理编码转换的问题。所以代码改成了这样:

// 为了避免“谁来释放内存”这种问题,转换函数直接返回了 std::string
std::string GbkToUtf8(const std::string &gbk_str);
std::string Utf8ToGbk(const std::string &utf8_str);

std::string GetOpenFilePath(const std::string &title) {
  // ...
  ofn.lpstrTitle = Utf8ToGbk(title).c_str();
  // ...
  
  return GbkToUtf8(path);
}

结果导致对话框标题一直是乱码。这是因为: Utf8ToGbk(title) 返回了一个临时对象,临时对象在当前表达式求值完成时会被销毁。导致后面调用 GetOpenFileNameA 时,lpstrTitle 指向的字符串其实已经被销毁了!

关于临时对象的销毁时机,C++ 标准文件 N486 6.7.7 描述如下:

Temporary objects are destroyed as the last step in evaluating the full-expression (6.9.1) that (lexically) contains the point where they were created.

解决方法很简单,只要将临时对象变为局部对象即可。

- ofn.lpstrTitle = Utf8ToGbk(title).c_str();
+ std::string t = Utf8ToGbk(title);
+ ofn.lpstrTitle = t.c_str();

顺带一提,将临时对象(函数的返回值)赋值给局部变量,并不会导致额外的一次拷贝构造。这就是所谓的返回值优化Return Value Optimization)。

posted @ 2022-09-02 17:10  fchen99  阅读(21)  评论(0)    收藏  举报