How to pass unique_ptr into a Function in C++?

Unique pointers (std::unique_ptr) is a Smart Pointer and was introduced in C++11. It provide exclusive ownership of a dynamically allocated memory. Unlike regular pointers or std::shared_ptr, std::unique_ptr ensures there is only one owner for the allocated memory, making it a safe and efficient way to handle dynamic memory in C++. In this article, we will discuss how we can pass unique pointers into a function without running into common pitfalls.

Moving unique_ptr to a function

When it comes to function parameters, one might assume that you could pass a unique pointer like any other variable. However, because unique pointers are designed to be unique, they cannot be copied; they can only be moved. This property has important implications for how we pass them to functions or use them as return values.

Let’s understand this with an example.

For example, we have a function that accepts a unique_ptr like this,

void processValue(std::unique_ptr<int> ptr)
{
    // Print information about the ptr (in our case, an int value)
    std::cout << "Integer value: " << *ptr << std::endl;
}

Now, if we try to call this function and pass unique_ptr by value, it will be a compile-time error because we cannot pass a unique_ptr object by value like this.

// This line would cause a compile-time error
// Because we can not create a copy of  unique pointer
processValue(ptrObj);

Error

example1.cpp: In function ‘int main()’:
example1.cpp:16:17: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’
   16 |     processValue(ptrObj);
      |     ~~~~~~~~~~~~^~~~~~~~
In file included from /usr/include/c++/11/memory:76,
                 from example1.cpp:2:
/usr/include/c++/11/bits/unique_ptr.h:468:7: note: declared here
  468 |       unique_ptr(const unique_ptr&) = delete;
      |       ^~~~~~~~~~
example1.cpp:4:40: note:   initializing argument 1 of ‘void processValue(std::unique_ptr<int>)’
    4 | void processValue(std::unique_ptr<int> ptr)

We cannot make copies of unique_ptr. But we can move the unique_ptr object. So, the correct way to pass a unique_ptr object into a function is by moving it using the std::move() function like this:

// We can move unique_ptr object
processValue(std::move(ptrObj)); // This is correct

// Once moved, unique_ptr object is empty and internally points to null
if (!ptrObj)
{
    std::cout << "ptrObj is now null." << std::endl;
}

Output:

Integer value: 10
ptrObj is now null.

Once the unique_ptr object is moved, the memory it was holding is transferred, and then it points to nullptr. Therefore, we should not use the unique_ptr after moving it.

Also, instead of creating a unique_ptr object and then moving it, we can also use the make_unique function to directly create and forward the unique_ptr object while calling the function, like this:

// We can move unique_ptr object
processValue(std::make_unique<int>(30)); // This is correct

Let’s see the complete example,

#include <iostream>
#include <memory>

void processValue(std::unique_ptr<int> ptr)
{
    // Print information about the ptr (in our case, an int value)
    std::cout << "Integer value: " << *ptr << std::endl;
}

int main()
{
    std::unique_ptr<int> ptrObj = std::make_unique<int>(10);

    // This line would cause a compile-time error
    // Because we can not create a copy of  unique pointer
    //processValue(ptrObj);

    // We can move unique_ptr object
    processValue(std::move(ptrObj)); // This is correct

    // Once moved, unique_ptr object is empty and internally points to null
    if (!ptrObj)
    {
        std::cout << "ptrObj is now null." << std::endl;
    }

    // We can move unique_ptr object
    processValue(std::make_unique<int>(30)); // This is correct

    return 0;
}

Output:

Integer value: 10
ptrObj is now null.
Integer value: 30

In the above code, if you uncomment the processValue(ptrObj) line, the compiler will throw an error because it attempts to make a copy of the unique pointer. By using std::move, we transfer ownership of the memory to the function, leaving the original pointer ptrObj empty (nullptr).

Important Points about unique_ptr:

  1. No Copies Allowed: Unique pointers cannot be copied, only moved. Attempting to copy a std::unique_ptr will result in a compiler error.
  2. Ownership Transfer: When you move a std::unique_ptr to a function, the original pointer is set to nullptr, and ownership is transferred to the receiving function parameter.
  3. Temporary Objects: When a temporary unique pointer is passed to a function, it is moved into the function parameter, and its memory is managed within the function scope.

Summary

By understanding these rules and behaviours, you can use unique pointers effectively in your C++ programs, ensuring resource safety and efficient memory management.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top