函数返回LPCTSTR指针后,如何正确释放动态分配的内存?
问题描述
我查过类似问题,但它们都假设返回类型是指针。刚接触C++,表述不当还请见谅。
我写了下面这个函数,用来把std::string转成LPCTSTR(等价于wchar_t*):
//Converts string to LPCTSTR for dialog box LPCTSTR CAnotherAttemptDlg::convertToLPCTSTR(std::string conversion) { size_t convertedChars = 0; const size_t newsizew = conversion.size() + 1; wchar_t* wcstring = new wchar_t[newsizew]; mbstowcs_s(&convertedChars, wcstring, newsizew, conversion.c_str(), _TRUNCATE); return wcstring; }
现在的问题是,我返回的是指针,但没法在函数外通过删除赋值后的指针来释放内存(当前代码里的delete[]在return之后根本不会执行,已经造成内存泄漏)。请问最佳解决方案是什么?
补充:调用该函数的代码如下:
BOOL CAnotherAttemptDlg::OnInitDialog() { string settingsLine; LPCTSTR defStrPath = L""; // = "C:\\Ste\\Runtime\\TestFiles"; LPCTSTR defEndPath = L""; //grabs default start and end paths from FileTrams.ini, parses it into proper variables ifstream settingsStream("FileTrans.ini"); while (getline(settingsStream, settingsLine)) { string delimiter = "="; string tokenOne = settingsLine.substr(0, settingsLine.find(delimiter)); string tokenTwo = settingsLine.substr(settingsLine.find(delimiter) + 1, '10'); if (tokenOne == "startPath") { defStrPath = convertToLPCTSTR(tokenTwo); } else if (tokenOne == "endPath") { defEndPath = convertToLPCTSTR(tokenTwo); } } settingsStream.close(); CDialogEx::OnInitDialog(); SetWindowLong(this->m_hWnd, GWL_STYLE, GetWindowLong(this->m_hWnd, GWL_STYLE) | WS_SYSMENU); // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here SetDlgItemText(IDC_MFCEDITBROWSE1, defStrPath); SetDlgItemText(IDC_MFCEDITBROWSE2, defEndPath); return TRUE; // return TRUE unless you set the focus to a control delete[] defStrPath; delete[] defEndPath; }
解决方案
首先明确:LPCTSTR本质就是指针类型(Unicode环境下是const wchar_t*),你没法让它不是指针,但可以用C++的RAII机制自动管理内存,彻底避免手动new/delete带来的泄漏问题。
方案1:用std::wstring代替手动分配的wchar_t*
修改转换函数返回std::wstring,需要LPCTSTR时调用c_str()方法即可,std::wstring会自动管理内存:
std::wstring CAnotherAttemptDlg::convertToLPCTSTR(std::string conversion) { size_t convertedChars = 0; const size_t newsizew = conversion.size() + 1; // 初始化对应长度的宽字符串 std::wstring wcstring(newsizew, L'\0'); mbstowcs_s(&convertedChars, &wcstring[0], newsizew, conversion.c_str(), _TRUNCATE); // 调整字符串到实际转换后的有效长度 wcstring.resize(convertedChars); return wcstring; }
然后修改调用代码,把defStrPath和defEndPath改成std::wstring类型:
BOOL CAnotherAttemptDlg::OnInitDialog() { string settingsLine; std::wstring defStrPath = L""; std::wstring defEndPath = L""; ifstream settingsStream("FileTrans.ini"); while (getline(settingsStream, settingsLine)) { string delimiter = "="; size_t delimiterPos = settingsLine.find(delimiter); if (delimiterPos == std::string::npos) continue; // 跳过无效配置行 string tokenOne = settingsLine.substr(0, delimiterPos); // 原代码用'10'作为长度是错误的,直接截取到字符串末尾 string tokenTwo = settingsLine.substr(delimiterPos + 1); if (tokenOne == "startPath") { defStrPath = convertToLPCTSTR(tokenTwo); } else if (tokenOne == "endPath") { defEndPath = convertToLPCTSTR(tokenTwo); } } settingsStream.close(); CDialogEx::OnInitDialog(); SetWindowLong(this->m_hWnd, GWL_STYLE, GetWindowLong(this->m_hWnd, GWL_STYLE) | WS_SYSMENU); SetIcon(m_hIcon, TRUE); SetIcon(m_hIcon, FALSE); // 调用c_str()获取LPCTSTR类型的指针 SetDlgItemText(IDC_MFCEDITBROWSE1, defStrPath.c_str()); SetDlgItemText(IDC_MFCEDITBROWSE2, defEndPath.c_str()); return TRUE; }
这样函数结束时std::wstring会自动释放内存,完全不用手动delete,彻底解决泄漏问题。
方案2:直接在调用处完成转换(跳过单独函数)
如果这个转换只在OnInitDialog里使用,也可以直接在循环内完成转换,省掉单独的函数:
BOOL CAnotherAttemptDlg::OnInitDialog() { string settingsLine; std::wstring defStrPath = L""; std::wstring defEndPath = L""; ifstream settingsStream("FileTrans.ini"); while (getline(settingsStream, settingsLine)) { string delimiter = "="; size_t delimiterPos = settingsLine.find(delimiter); if (delimiterPos == std::string::npos) continue; string tokenOne = settingsLine.substr(0, delimiterPos); string tokenTwo = settingsLine.substr(delimiterPos + 1); if (tokenOne == "startPath" || tokenOne == "endPath") { size_t convertedChars = 0; const size_t newsizew = tokenTwo.size() + 1; std::wstring temp(newsizew, L'\0'); mbstowcs_s(&convertedChars, &temp[0], newsizew, tokenTwo.c_str(), _TRUNCATE); temp.resize(convertedChars); if (tokenOne == "startPath") { defStrPath = std::move(temp); // 用move避免拷贝 } else { defEndPath = std::move(temp); } } } settingsStream.close(); // 后续初始化代码不变 CDialogEx::OnInitDialog(); SetWindowLong(this->m_hWnd, GWL_STYLE, GetWindowLong(this->m_hWnd, GWL_STYLE) | WS_SYSMENU); SetIcon(m_hIcon, TRUE); SetIcon(m_hIcon, FALSE); SetDlgItemText(IDC_MFCEDITBROWSE1, defStrPath.c_str()); SetDlgItemText(IDC_MFCEDITBROWSE2, defEndPath.c_str()); return TRUE; }
额外注意点
- 原代码中
substr的第二个参数用'10'是错误的——substr的第二个参数是截取长度,'10'是字符,对应ASCII值49,会导致只截取49个字符,正确做法是去掉第二个参数,直接截取到字符串末尾。 - 原代码里的
delete[]语句在return TRUE之后,永远不会执行,这是典型的内存泄漏场景,用std::wstring这类RAII容器可以彻底避免这类问题。
内容的提问来源于stack exchange,提问作者Jeremy Hahn
相关产品推荐
相关产品推荐

