Contents

C++ quick tips: shared_ptr aliasing constructor

C++20 provides a small addition to shared_ptr constructor overloads set which is called an aliasing constructor. Quoting after cppreference:

The aliasing constructor: constructs a shared_ptr which shares ownership information with the initial value of r, but holds an unrelated and unmanaged pointer ptr. If this shared_ptr is the last of the group to go out of scope, it will call the stored deleter for the object originally managed by r…

How does it work?

The constructor takes two arguments:

1
2
template< class Y >
shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept;

There are two pointers:

  • r - which is managed and defines the lifetime,
  • ptr - which is unmanaged and provides the pointer value.

shared_ptr constructed using the aliasing constructor will always return the value of ptr but its lifetime is defined by r instead.

Example

For the purpose of the example, I’m gonna first define a Value class with some print statements so, the lifetime of the object is easier to track:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
template <typename T>
class Value {
public:
    explicit Value(T v) : value{v} {
        std::cout << ">Value(" << v << ")" << std::endl;
    }

    ~Value() {
        std::cout << "<Value" << std::endl;
    }

    T get() const { return value; }

private:
    T value;
};

Now, I’m gonna define a wrapper for an Value<int>:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20

class IntValW :  public std::enable_shared_from_this<IntValW> {
public:
    IntValW(int v) :
        value{std::make_unique<Value<int>>(v)}
    {
        std::cout << ">IntValW" << std::endl;
    }

    ~IntValW() {
        std::cout << "<IntValW" << std::endl;
    }

    std::shared_ptr<Value<int>> getValue() const {
        return { shared_from_this(), value.get() };
    }

private:
    std::unique_ptr<Value<int>> value;
};

The getValue member function is the interesting bit here. It creates a shared_ptr using an aliasing constructor. The returned pointer is of type shared_ptr<Value<int>>, but its lifetime is defined by the lifetime of the IntValW wrapper - since it’s using shared_from_this().

Let’s have a look on how this works in practice:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int main(int argc, const char *argv[])
{
    std::cout << ">main" << std::endl;

    auto x = std::make_shared<IntValW>(123);

    {
        std::cout << ">scope" << std::endl;
        auto v = x->getValue();
        std::cout << "<scope" << std::endl;
    }

    std::cout << "<main" << std::endl;
    return 0;
}

This program produces the following output:

1
2
3
4
5
6
7
8
>main
>Value(123)
>IntValW
>scope
<scope
<main
<IntValW
<Value

It’s clear that v is still alive even though the pointer is no longer accessible once out of scope. It only gets destroyed once x goes out of scope.

Let’s have a look on another example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int main(int argc, const char *argv[])
{
    std::cout << ">main" << std::endl;
    std::shared_ptr<Value<int>> v;

    {
        std::cout << ">scope" << std::endl;
        auto x = std::make_shared<IntValW>(123);
        v = x->getValue();
        std::cout << "<scope" << std::endl;
    }

    std::cout << "<main" << std::endl;
    return 0;
}

This is a converse situation where v outlives the x.

Once we run the code, it’ll produce the following output:

1
2
3
4
5
6
7
8
>main
>scope
>Value(123)
>IntValW
<scope
<main
<IntValW
<Value

It’s visible here that v has prolonged the lifetime of x even though on the surface, it’s only pointing to Value<int> and not IntValW explicitly.

Applications

I imagine a variety of applications for this feature. One of which is the one that I described here, which is to provide a managed access to unmanaged class members. Amongst many, other applications might be:

  • memoization,
  • caching,
  • lifetime management across multiple threads,

Example code

The discussed code is available via the godbolt link.