What I want to show here is not a new or surprising trick, but simply the consistent use of the possibilities available since C++14 to use a clean memory management for the return of dynamically allocated memory. The main use case are certainly strings. My string class serves here only as an easy to understand example to explain the approach.
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
14 String value = provider();
15
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:
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 via operator=
or reset()
.
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 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 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;
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