Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++14

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

1.94/5 (5 votes)
27 Dec 2020CPOL4 min read 17.3K  
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. 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:

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 }

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:

C++
/// <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 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:

C++
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:

C++
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:

C++
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:

C++
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.

Assignment operators

And some more exemplary method implementations:

C++
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.

Pitfall

But be careful in case the string is not passed as argument into the function/method but is created within the function/method:

C++
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

License

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