How to Return a String Class from a C++ Function/Method






1.94/5 (5 votes)
Use STL and C++14 to return a dynamically created string from a function/method, that is automatically garbage collected.
Introduction
I had the problem of wanting to return a dynamically generated string from a C++ API method without forcing the user of the API to release the dynamically generated string himself.
Since my C++ knowledge was very rusty after many years of C# programming, I struggled with it and wrote a mediator class and a minimal garage collector suitable for this purpose until I realized that the STL already had all that. All those who feel at home in the STL and for whom std::unique_ptr<T>
is the best companion can actually stop here to read. Who nevertheless expected more should perhaps read on in article A new approach to memory management that solves the issues with shared_ptrs by Achilleas Margaritis or Ideas from a smart pointer(part 2). size == sizeof(std::shared_ptr)/2 by weibing.
What follows now is rather meant for those whose, C++ knowledge is not yet as well developed or - like me - is very rusty.
Background
Update: The approach described here is not very elegant and some may classify it as unclean - and quite generally it is certainly so. There is an even better approach then to return a std::unique_ptr<T>
- as Mircea Neacsu and Michaelgor stated out in ther commets below. So - why is this tip still here?
The limits of an elegant and clean C++ solution
Modern C++ provides the direct return of class instances. I call it "modern" C++ because the return of class instances from functions/methods requires the implementation of a move assignment operator.
Assume we have the following simple situation:
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 }
This works fine, if class String
provides a move assignment operator that can be used at line 14:
String value = provider();
Such a move assignment operator, that enables us to return a class instance, might look like:
/// <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;
}
So far, so good. The disadvantage of this solution is that the implementation of a move assignment operator can be much more expensive for classes with significantly more member variables. Or the class/a base class of the class, for which a move assignment operator is needed, is in a library that cannot or should not be changed.
So please take the following approach as a suggestion in case the implementation of a move assignment operator is not desired or not possible.
The idea
I have tagged this tip with C++14. But what I show here can also be done with the old auto_ptr<T>
, which is deprecated with C++11 and deleted with C++17. And it also works with C++11, you just have to do it without std::make_unique<T>()
.
The std::unique_ptr<T>
documentation contains:
The std::unique_ptr
is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr
goes out of scope.
The object is disposed of, using the associated deleter when either of the following happens:
- the managing
unique_ptr
object is destroyed - the managing
unique_ptr
object is assigned to another pointer viaoperator=
orreset()
.
And:
The std::unique_ptr
is commonly used to manage the lifetime of objects, including:
- providing exception safety to classes and functions that handle objects with dynamic lifetime, by guaranteeing deletion on both normal exit and exit through exception
- passing ownership of uniquely-owned objects with dynamic lifetime into functions
- acquiring ownership of uniquely-owned objects with dynamic lifetime from functions
- as the element type in move-aware containers, such as std::vector, which hold pointers to dynamically-allocated objects (e.g., if polymorphic behavior is desired)
In other words: Everything I dreamed of is already available in the STL
: Garbage collection and the possibility to return dynamically allocated memory from a function/metode.
Using the Code
Sample
Here is an example of use:
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;
}
In this example, the method GetDirectoryName()
of the C++ wrapper class CPath
calls the plain old C API function CPath_GetCoDirectoryName()
and returns the result as a std::unique_ptr<String>
.
For an implementation with C++11, std::make_unique<String>(wszBuffer)
has to be omitted and the corresponding line will look like this:
std::unique_ptr<String> pMediator(new String(wszBuffer));
Please read the tip Improving on shared_ptr by AlexZakharenko for details why std::make_unique<String>(wszBuffer)
should be used.
The consumer code of the sample CPath
wrapper class GetDirectoryName()
method will look like this:
std::unique_ptr<String> strText = CPath::GetDirectoryName(strPath);
SetToolTip(_pToolBar, uiCommandID, strText.get()->Value());
The complete code of the String
class can be found in the article, A basic icon editor running on ReactOS (and consequently on Windows XP and newer versions).
Constructors
Here are a few exemplary constructor implementations:
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;
}
The String
class provides three initializing constructors:
- In the case that the
string
is a return value from a function/method. - In case the ownership of the character array should not be taken over.
- In case the ownership of the character array should be taken over.
Assignment operators
And some more exemplary method implementations:
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;
}
The String
class also provides three assignment operators:
- In the case that the
string
is a return value from a function/method. - In case the ownership of the character array should not be taken over.
- In case the ownership of the character array should be taken over.
Pitfall
But be careful in case the string
is not passed as argument into the function/method but is created within the function/method:
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);
}
In this case, a sufficient life span of the std::unique_ptr<String>
must be ensured.
Points of Interest
How can my code be "clean", in the sense of "free of memory holes", and at the same time, convenient to use?
History
- 25th October, 2020: Initial version
- 26th October, 2020: Update 1
- 28th December, 2020: Update 2