Click here to Skip to main content
14,693,642 members
Articles » Languages » C / C++ Language » Memory Management
Tip/Trick
Posted 25 Oct 2020

Tagged as

Stats

2.7K views
5 bookmarked

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

Rate me:
Please Sign up or sign in to vote.
2.23/5 (4 votes)
26 Oct 2020CPOL
Use STL and C++14 to return a dynamically created string from a function/method, that is automatically garbage collected
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.

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.


Update: There is an even better approach then to return a std::unique_ptr<T>.

Thankfully Mircea Neacsu pointed out to me that with modern C++ compilers it is possible to return class instances directly and to evaluate them effectively (fast and memory saving) within the caller's code using the move assignment operator. Details can be found in the comment of Mircea Neacsu and my answer.


Background

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

My application of this technique is essentially limited to strings. 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). Here are a few exemplary method 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:

  1. In the case that the string is a return value from a function/method.
  2. In case the ownership of the character array should not be taken over.
  3. In case the ownership of the character array should be taken over.

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:

  1. In the case that the string is a return value from a function/method.
  2. In case the ownership of the character array should not be taken over.
  3. In case the ownership of the character array should be taken over.

But there is a pitfall - 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, more convenient to use?

History

  • 25th October, 2020: Initial version
  • 26th October, 2020: Update 1

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Steffen Ploetz
CEO Ploetz + Zeller GmbH
Germany Germany
No Biography provided

Comments and Discussions

 
QuestionMy Vote of 1 Pin
TheGreatAndPowerfulOz29-Oct-20 7:54
MemberTheGreatAndPowerfulOz29-Oct-20 7:54 
Suggestionstd::string objects are already dynamically allocated Pin
Mircea Neacsu25-Oct-20 10:56
MemberMircea Neacsu25-Oct-20 10:56 
GeneralRe: std::string objects are already dynamically allocated Pin
Steffen Ploetz26-Oct-20 0:43
professionalSteffen Ploetz26-Oct-20 0:43 
GeneralRe: std::string objects are already dynamically allocated Pin
Michaelgor26-Oct-20 9:59
MemberMichaelgor26-Oct-20 9:59 
GeneralRe1: std::string objects are already dynamically allocated Pin
Steffen Ploetz26-Oct-20 8:41
professionalSteffen Ploetz26-Oct-20 8:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.