如何从 C++ 函数/方法返回字符串类






1.94/5 (5投票s)
使用 STL 和 C++14 从函数/方法返回动态创建的字符串,该字符串会被自动垃圾回收。
引言
我遇到了一个问题,希望从 C++ API 方法返回一个动态生成的字符串,而无需强制 API 用户自己释放动态生成的字符串。
由于多年 C# 编程后,我的 C++ 知识已经非常生疏,我为此苦苦挣扎,并编写了一个中介类和一个适合此目的的最小化垃圾收集器,直到我意识到 STL 已经具备了所有这些功能。所有熟悉 STL 并且认为 std::unique_ptr<T>
是最佳伴侣的人,实际上可以到此为止了。如果仍然期望更多,也许可以继续阅读 解决 shared_ptr 问题的内存管理新方法 这篇文章,作者是 Achilleas Margaritis,或者 智能指针的构思(第 2 部分)。size == sizeof(std::shared_ptr)/2,作者是 weibing。
接下来要讲的内容,更多是为那些 C++ 知识尚不发达,或者像我一样,已经非常生疏的人准备的。
背景
更新:此处描述的方法并不十分优雅,有些人可能会认为它不干净——而且总的来说,它确实如此。比返回 std::unique_ptr<T>
还有一个更好的方法——正如 Mircea Neacsu 和 Michaelgor 在下方的评论中指出的那样。那么——为什么这个技巧仍然在这里?
优雅且干净的 C++ 解决方案的局限性
现代 C++ 提供了直接返回类实例的功能。我称之为“现代”C++,因为从函数/方法返回类实例需要实现移动赋值运算符。
假设我们有以下简单的情况
01 String provider()
02 {
03 LPWSTR pszRawData;
04 int iLengt = ::AnyOldFashionedCCall();
05 pszRawData = new WCHAR[pszRawData];
06 String s(pszRawData);
07 delete pszRawData;
08 return s;
09 }
10
11 void consumer()
12 {
13 /* do something */
14 String value = provider();
15 /* do something */
16 }
如果 class String
提供了第 14 行使用的移动赋值运算符,那么这将正常工作。
String value = provider();
一个可以让我们返回类实例的移动赋值运算符可能看起来像
/// <summary>
/// Assigns a (fresh) value to this instance of this <c>String</c> instance.
/// Acts like a move constructor (acquires the data / mediator's data will be incorporated).
/// </summary>
/// <param name="rstrMediator">The source of the character sequence to be assigned.</param>
/// <returns>Returns a self reference.</returns>
/// <remarks>Acquires the data of the mediator.
/// Mediator will be incorporated and empty after this operation.</remarks>
String& operator=(String&& rstrMediator);
{
if (this != &rstrMediator)
{
if (_wszText != NULL)
::CoTaskMemFree((LPVOID)_wszText);
_wszText = rstrMediator._wszText;
rstrMediator._wszText = (LPCOWSTR)NULL;
}
return *this;
}
到目前为止,一切都很好。这种解决方案的缺点是,对于具有显著更多成员变量的类,实现移动赋值运算符的开销可能非常大。或者,类/类的基类需要移动赋值运算符,但该类在无法或不应更改的库中。
因此,如果不想实现移动赋值运算符,或者无法实现,请将以下方法视为一个建议。
想法
我将此技巧标记为 C++14。但我在这里展示的内容也可以用旧的 auto_ptr<T>
来实现,它在 C++11 中被弃用,在 C++17 中被删除。它也适用于 C++11,只是您需要不使用 std::make_unique<T>()
。
std::unique_ptr<T>
文档包含
std::unique_ptr
是一个智能指针,它通过指针拥有并管理另一个对象,并在 unique_ptr
离开作用域时处理该对象。
该对象在以下任一情况发生时,使用关联的删除器进行处理:
- 所管理的
unique_ptr
对象被销毁 - 所管理的
unique_ptr
对象通过operator=
或reset()
被赋值给另一个指针。
并且
std::unique_ptr
通常用于管理对象的生命周期,包括:
- 通过保证在正常退出和异常退出时都进行删除,为处理具有动态生命周期的对象的类和函数提供异常安全性
- 将独占拥有的动态生命周期对象的拥有权传递给函数
- 从函数获取独占拥有的动态生命周期对象的拥有权
- 作为可移动容器(如 std::vector)的元素类型,这些容器保存指向动态分配对象的指针(例如,如果需要多态行为)
换句话说:我曾经梦想的一切都已经可以在 STL
中找到:垃圾回收以及从函数/方法返回动态分配内存的可能性。
Using the Code
示例
这是一个使用示例
static std::unique_ptr<String> CPath::GetDirectoryName(String& strPath)
{
LPCOWSTR_TRANSFERRED wszBuffer = CPath_GetCoDirectoryName(strPath.Value());
std::unique_ptr<String> pMediator = std::make_unique<String>(wszBuffer);
return pMediator;
}
在此示例中,C++ 包装类 CPath
的 GetDirectoryName()
方法调用普通的 C API 函数 CPath_GetCoDirectoryName()
,并将结果作为 std::unique_ptr<String>
返回。
对于 C++11 实现,必须省略 std::make_unique<String>(wszBuffer)
,相应的行将如下所示:
std::unique_ptr<String> pMediator(new String(wszBuffer));
请阅读 改进 shared_ptr 这篇由 AlexZakharenko 撰写的技巧,了解为什么应该使用 std::make_unique<String>(wszBuffer)
。
示例 CPath
包装类 GetDirectoryName()
方法的消费者代码将如下所示:
std::unique_ptr<String> strText = CPath::GetDirectoryName(strPath);
SetToolTip(_pToolBar, uiCommandID, strText.get()->Value());
String
类的完整代码可以在文章 在 ReactOS 上运行的基本图标编辑器(因此也适用于 Windows XP 及更高版本) 中找到。
构造函数
以下是一些示例构造函数实现
String::String(std::unique_ptr<String> mediator)
{
if (mediator != NULL)
_wszText = (LPCOWSTR)CString_CoCopy(mediator.get()->Value());
else
_wszText = (LPCOWSTR)NULL;
}
String::String(LPCOCWSTR /* weak */ wszText)
{
_wszText = (LPCOWSTR)CString_CoCopy(wszText);
}
String::String(LPCOWSTR_TRANSFERRED wszText)
{
_wszText = (LPCOWSTR)wszText;
}
String
类提供三个初始化构造函数
- 在字符串是函数/方法的返回值的情况下。
- 在不应接管字符数组所有权的情况下。
- 在应接管字符数组所有权的情况下。
赋值运算符
以及更多示例方法实现
String& String::operator=(std::unique_ptr<String> mediator)
{
if (_wszText != NULL)
::CoTaskMemFree((LPVOID)_wszText);
if (mediator != NULL)
_wszText = (LPCOWSTR)CString_CoCopy(mediator.get()->Value());
else
_wszText = (LPCOWSTR)NULL;
return *this;
}
String& String::operator=(LPCOCWSTR /* weak */ wszText)
{
_wszText = (LPCOWSTR)CString_CoCopy(wszText);
return *this;
}
String& String::operator=(const String& strText)
{
_wszText = (LPCOWSTR)CString_CoCopy(strText.Value());
return *this;
}
String
类还提供三个赋值运算符
- 在字符串是函数/方法的返回值的情况下。
- 在不应接管字符数组所有权的情况下。
- 在应接管字符数组所有权的情况下。
误区
但请注意,在字符串不是作为参数传递到函数/方法中,而是在函数/方法内部创建的情况下:
std::unique_ptr<String> String::Format(std::unique_ptr<String> strFormatMediator,
CRttiProvider& s00, CRttiProvider& s01,
CRttiProvider& s02, CRttiProvider& s03)
{
if (strFormatMediator.get()->IsNullOrEmpty())
return NULL;
// Keep the data alive during this method's whole lifetime.
std::unique_ptr<String> pS00 = s00.ToString();
std::unique_ptr<String> pS01 = s01.ToString();
std::unique_ptr<String> pS02 = s02.ToString();
std::unique_ptr<String> pS03 = s03.ToString();
LPCWSTR pXX[4];
pXX[0] = pS00.get()->Value();
pXX[1] = pS01.get()->Value();
pXX[2] = pS02.get()->Value();
pXX[3] = pS03.get()->Value();
return Format(strFormatMediator.get()->Value(), (LPCWSTR*)pXX, (WORD)4);
}
在这种情况下,必须确保 std::unique_ptr<String>
有足够的生命周期。
关注点
我的代码如何才能“干净”,即“没有内存泄漏”,同时又方便使用?
历史
- 2020年10月25日:初版
- 2020年10月26日:更新 1
- 2020年12月28日:更新 2