Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 12 – Move semantics and smart pointers

Expressing ownership in C++ function signatures

Choose parameter and return types that say who owns an object after a call: references lend, unique_ptr hands over, shared_ptr shares.

  • Advanced
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: Each parameter type says who owns the object · Types turn an ownership crash into a compile error · C++26 adds owners that copy like values · Practise. With a pass: 2 in-depth parts and 3 interview answers, about 12 minutes.

What you will learn

  • Choose parameter types that state who owns what after a call
  • Design factories that return ownership through std::unique_ptr
  • Use C++26 std::indirect and std::polymorphic where the library provides them

Before you start

void park(Vehicle* v) compiles whether the garage deletes the vehicle later or only looks at it, and in this lesson’s first run it deleted a car that lived on the stack. A function’s signature can say who owns an object after the call, and modern C++ uses it to say exactly that.

A reference or a raw pointer lends an object: the function uses it during the call and never deletes it. A std::unique_ptr taken by value, a sink parameter: A parameter that takes over the object it is given, such as a std::unique_ptr taken by value: the caller writes std::move and is left empty, and the function keeps or destroys the object., hands the object over, and one returned from a factory function: A function that creates and returns an object, often a static member that checks its arguments first and returns std::expected or std::optional, so that the class can keep its constructor private and every object valid. More in “Designing value types and strong types in C++” hands a new object to the caller. A std::shared_ptr taken by value means the function keeps a share. With these types the compiler checks the rules that comments used to state, and a reader can tell from a declaration alone whether a call transfers, shares or only borrows an object.

Each parameter type says who owns the object

Three panels: a caller lends a Widget to draw(), hands it over to park() with std::move, and shares it with keep().LendHand overSharecaller,still the ownerdraw(const Widget&)caller,left emptypark(std::unique_ptr<Widget>)caller,still an ownerkeep(std::shared_ptr<Widget>)a referencestd::move(w)a copy

Lend, hand over or share

Text description of the diagram

The diagram has three panels, one under the other. In each, a box for the caller has an arrow down to a function that receives the caller's Widget.

  1. Lend: the caller, still the owner, passes "a reference" to draw(const Widget&), which uses the widget during the call only.
  2. Hand over: the caller passes the widget with std::move(w) to park(std::unique_ptr<Widget>), which owns it from then on; the caller is left empty.
  3. Share: the caller passes "a copy" of its shared_ptr to keep(std::shared_ptr<Widget>), so the caller and the function both own the widget.

The diagram shows the three things a call can do with the caller’s widget. The C++ Core Guidelines give each signature a rule; the table uses Widget for any class.

Signature What it tells the caller Rule
void f(const Widget&) Reads the widget during the call and keeps nothing F.7
void f(Widget&) May change the widget, and keeps nothing F.7
void f(const Widget*) Like the reference, but “no widget” (nullptr) is a valid argument F.60
void f(std::unique_ptr<Widget>) Takes ownership: the caller writes std::move(w) and is left empty R.32
void f(std::unique_ptr<Widget>&) May replace or reset the caller’s widget R.33
void f(std::unique_ptr<Widget>&&) May take ownership, and the call alone does not show whether it did F.18
void f(std::shared_ptr<Widget>) Keeps a share: it stores or passes on a copy R.34
void f(std::shared_ptr<Widget>&) May point the caller’s shared_ptr at another widget R.35
void f(const std::shared_ptr<Widget>&) Might keep a share; prefer const Widget& if it never does R.36
std::unique_ptr<Widget> make() The caller owns the new widget F.26
const Widget* find() const Points into something another object owns; never delete it F.42

Two rules cover most code. A function that only uses an object takes a reference, or a pointer when the object is optional; smart pointers appear in a signature only when the call changes who owns the object (F.7, R.30). And ownership never travels through a raw pointer or a reference (I.11), because nothing in such a type says that it does. Step through the questions that pick a type:

Interactive · stage by stage Which type says what the function does with the object Step through six questions about a function. Each stage asks one and names the type that states the answer.
Which type says what the function does with the object, step 6 of 6. Showing Only used during the call?, May there be no widget?, Taken over for good?, Replaces the caller's?, Kept alongside the caller?, Handing out a new one?. In focus: Handing out a new one?.Only used during the call?const Widget&, or Widget&May there be no widget?const Widget*, maybe nullptrTaken over for good?std::unique_ptr<Widget>Replaces the caller's?std::unique_ptr<Widget>&Kept alongside the caller?std::shared_ptr<Widget>Handing out a new one?returns unique_ptr<Widget>

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Only used. A function that reads the widget during the call and keeps nothing takes const Widget&, or Widget& if it changes it (F.7). Any caller can pass any widget, whatever owns it.
  2. Maybe no widget. When "no widget" is a valid argument, take const Widget* and handle nullptr (F.60). The pointer still owns nothing.
  3. Taken over. A function that keeps or destroys what it is given takes std::unique_ptr<Widget> by value (R.32). The caller writes std::move(w) and is left empty, so the hand-over shows at the call.
  4. Replacing the caller's. A function that may make the caller's pointer own another widget, or reset it, takes std::unique_ptr<Widget>& (R.33).
  5. Kept alongside. A function that stores a copy, or passes one on, while the caller goes on using the widget takes std::shared_ptr<Widget> by value (R.34).
  6. Handed out. A factory returns std::unique_ptr<Widget>: the caller owns the new widget (F.26) and may keep it unique or turn it into a shared_ptr.

Checkpoint

Check: What each parameter type says

  1. Question 1 of 2 park takes std::unique_ptr<Widget> by value. After park(std::move(w));, what does w hold?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Nothing, it is empty

    Taking a std::unique_ptr by value takes ownership: the caller writes std::move(w) and is left empty.

  2. Question 2 of 2 A library function void adopt(Widget* w) deletes w when it is done with it. What should its signature be?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: void adopt(std::unique_ptr<Widget> w), because ownership never travels through a raw pointer

    Smart pointers appear in a signature exactly when the call changes who owns the object, and a function that deletes what it is given takes it over.

Types turn an ownership crash into a compile error

A garage stores vehicles. In the first version, park(Vehicle*) looks like it borrows the vehicle, yet the destructor deletes everything parked, so a caller who parks a vehicle that lives on the stack compiles cleanly and crashes later. The second version says the same things in its types:

The garage with unclear and with clear signatures

garage_unclear.cpp

// A garage whose signatures say nothing about ownership. Who deletes a parked vehicle, and may a caller delete what
// find() returns? Only the destructor, deep in the implementation, answers.
#include <print>
#include <string>
#include <utility>
#include <vector>

struct Vehicle {
    std::string plate;
    explicit Vehicle(std::string p) : plate(std::move(p)) {}
    virtual ~Vehicle() = default;
};

class Garage {
public:
    void park(Vehicle* v) { parked_.push_back(v); }
    Vehicle* find(const std::string& plate) {
        for (Vehicle* v : parked_)
            if (v->plate == plate) return v;
        return nullptr;
    }
    ~Garage() {
        for (Vehicle* v : parked_) delete v;  // so park() took ownership after all
    }

private:
    std::vector<Vehicle*> parked_;
};

int main() {
    Vehicle visitor("MH12CD5678");  // on the stack: main destroys it when it returns
    Garage garage;
    garage.park(new Vehicle("KA01AB1234"));  // right: the garage deletes it
    garage.park(&visitor);                   // compiles, and the garage will delete an object on the stack
    std::println("found {}", garage.find("MH12CD5678")->plate);
}

garage_clear.cpp

// The same garage with signatures that state ownership: park() takes it, find() lends a pointer, make_vehicle() hands
// a new vehicle to its caller.
#include <memory>
#include <print>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

struct Vehicle {
    std::string plate;
    explicit Vehicle(std::string p) : plate(std::move(p)) {}
    virtual ~Vehicle() = default;
};

class Garage {
public:
    void park(std::unique_ptr<Vehicle> v) { parked_.push_back(std::move(v)); }  // takes ownership
    const Vehicle* find(std::string_view plate) const {                         // lends: may be null, never delete it
        for (const auto& v : parked_)
            if (v->plate == plate) return v.get();
        return nullptr;
    }

private:
    std::vector<std::unique_ptr<Vehicle>> parked_;  // the vector deletes them: no destructor to write
};

std::unique_ptr<Vehicle> make_vehicle(std::string plate) {  // a factory: the caller owns the result
    return std::make_unique<Vehicle>(std::move(plate));
}

int main() {
    Garage garage;
    garage.park(make_vehicle("KA01AB1234"));
    auto car = make_vehicle("TN09EF4321");
    garage.park(std::move(car));  // the transfer is visible where it happens
    std::println("car after park: {}", car ? "still owns a vehicle" : "empty");
    if (const Vehicle* v = garage.find("TN09EF4321")) std::println("found {}", v->plate);
    std::println("found MH12CD5678: {}", garage.find("MH12CD5678") != nullptr);

    std::shared_ptr<Vehicle> pool_car = make_vehicle("DL03GH8765");  // a caller may share what a factory returns
    std::println("pool car owners: {}", pool_car.use_count());
}

garage_misuse.cpp

// The visitor mistake of garage_unclear.cpp, written against the clear signatures.
#include <memory>
#include <string>
#include <utility>
#include <vector>

struct Vehicle {
    std::string plate;
    explicit Vehicle(std::string p) : plate(std::move(p)) {}
    virtual ~Vehicle() = default;
};

class Garage {
public:
    void park(std::unique_ptr<Vehicle> v) { parked_.push_back(std::move(v)); }

private:
    std::vector<std::unique_ptr<Vehicle>> parked_;
};

int main() {
    Garage garage;
    Vehicle visitor("MH12CD5678");
    garage.park(&visitor);
}
A bad free at run time, then the same mistake refused by both compilers Python · garage_session.py
from session import run

# The unclear garage under AddressSanitizer, the clear one on both compilers, and the visitor mistake against the
# clear signatures (only the first error of each compiler is shown).
run("""
clang++ -std=c++23 -g -fsanitize=address garage_unclear.cpp -o garage_unclear && ./garage_unclear
clang++ -std=c++23 -Wall -Wextra garage_clear.cpp -o garage_clear && ./garage_clear
g++-16 -std=c++23 -Wall -Wextra garage_clear.cpp -o garage_clear-gcc && ./garage_clear-gcc
clang++ -std=c++23 garage_misuse.cpp 2>&1 | grep -m 1 'error:'
g++-16 -std=c++23 garage_misuse.cpp 2>&1 | grep -m 1 'error:'
""")

Output

$ clang++ -std=c++23 -g -fsanitize=address garage_unclear.cpp -o garage_unclear && ./garage_unclear
found MH12CD5678
==…==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0x… in thread T0
    #1 in Vehicle::~Vehicle() garage_unclear.cpp:11
    #2 in Garage::~Garage() garage_unclear.cpp:23
    #3 in Garage::~Garage() garage_unclear.cpp:22
    #4 in main garage_unclear.cpp:36
Address 0x… is located in stack of thread T0 at offset 32 in frame
    #0 in main garage_unclear.cpp:30
SUMMARY: AddressSanitizer: bad-free
[12 more lines of the report left out]
==…==ABORTING
Abort trap: 6
[exit status 134]
$ clang++ -std=c++23 -Wall -Wextra garage_clear.cpp -o garage_clear && ./garage_clear
car after park: empty
found TN09EF4321
found MH12CD5678: false
pool car owners: 1
$ g++-16 -std=c++23 -Wall -Wextra garage_clear.cpp -o garage_clear-gcc && ./garage_clear-gcc
car after park: empty
found TN09EF4321
found MH12CD5678: false
pool car owners: 1
$ clang++ -std=c++23 garage_misuse.cpp 2>&1 | grep -m 1 'error:'
garage_misuse.cpp:24:17: error: no viable conversion from 'Vehicle *' to 'std::unique_ptr<Vehicle>'
$ g++-16 -std=c++23 garage_misuse.cpp 2>&1 | grep -m 1 'error:'
garage_misuse.cpp:24:17: error: cannot convert 'Vehicle*' to 'std::unique_ptr<Vehicle>'

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 garage_session.py

Step through what happened:

Interactive · message by message The garage with unclear signatures, then with clear ones Step through the unclear garage, then the same mistake against the clear signatures. The sub-labels say who owns each vehicle.
The garage with unclear signatures, then with clear ones, step 6 of 6. Messages so far: main to Garage: park(new Vehicle(...)); main to Garage: park(&visitor); Garage to main: Vehicle* to the visitor; Garage to AddressSanitizer: delete &visitor; main to Garage: park(&visitor): error; Garage to main: const Vehicle* or nullptr.maincar after park: emptyGaragevector of unique_ptrAddressSanitizernothing to reportpark(new Vehicle(...))park(&visitor)Vehicle* to the visitordelete &visitorpark(&visitor): errorconst Vehicle* or nullptr

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. A vehicle from new. park(new Vehicle("KA01AB1234")) is how the unclear garage expects to be used: the destructor will delete everything parked.
  2. The visitor parks too. park(&visitor) compiles just as well, because Vehicle* says nothing about who owns the vehicle. The visitor lives on main's stack.
  3. find() works. find("MH12CD5678") returns the visitor, and the run printed found MH12CD5678. Nothing is wrong yet.
  4. The destructor deletes it. When main returns, ~Garage deletes every pointer it holds, the visitor among them. AddressSanitizer stops the program: attempting free on an address in the stack of thread T0, a bad free.
  5. The clear signature. Against park(std::unique_ptr<Vehicle>), the same park(&visitor) does not compile, with either compiler: a raw pointer does not convert to a std::unique_ptr by itself.
  6. Clear calls, clear results. With the clear garage, park(std::move(car)) leaves car empty, find() lends a const Vehicle*, which is nullptr when no plate matches, and the vector of unique_ptr deletes the vehicles, so the garage needs no destructor.

AddressSanitizer stops the unclear garage in its destructor: it deleted the visitor’s vehicle, which was never allocated with new, so the report is a bad free of an address in main‘s stack frame. The clear garage needs no destructor at all, because its vector of std::unique_ptr deletes the vehicles. find() returns a pointer to a const vehicle: a raw pointer owns nothing by the Core Guidelines’ convention (R.3), and const means that the caller cannot change the vehicle through it. The same visitor mistake against the clear signatures is a compile error on both compilers: a raw pointer does not convert to a std::unique_ptr by itself, so an ownership bug became a type error.

Factories return std::unique_ptr

make_vehicle() returns a std::unique_ptr, and that suits every caller. One can keep the vehicle unique or park it, and another can turn it into a std::shared_ptr, as pool_car does in the run, starting with one owner. The reverse is impossible: nothing turns a shared_ptr back into a unique_ptr. A factory returns std::shared_ptr only when it keeps a share itself, for example a cache that tracks the objects it hands out.

Checkpoint

Check: Ownership in the types, and factories

  1. Question 1 of 2 What did both compilers say about garage.park(&visitor); against the clear park(std::unique_ptr<Vehicle> v)?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A compile error, because a raw pointer does not convert to a std::unique_ptr by itself

    An ownership bug became a type error: the mistake is refused where it is written.

  2. Question 2 of 2 Why could pool_car hold what make_vehicle() returned in a std::shared_ptr<Vehicle>?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A std::unique_ptr converts to a std::shared_ptr, which then starts with one owner

    A caller can turn a factory's unique_ptr into a shared_ptr in one step; nothing turns a shared_ptr back into a unique_ptr.

C++26 adds owners that copy like values

Some classes need a heap-allocated part yet should copy like plain values. C++26 adds two owners for them. std::indirect holds one object of a known type and copies that object whenever it is copied. std::polymorphic holds an object of any class derived from a base, and a copy copies the derived object. GCC 16’s standard library ships both, while Apple clang 21’s libc++ does not have them yet. Step through a copy of each:

Interactive · step by step Copying owners that copy like values Step through a copy of each owner. Solid arrows own; each copy gets an object of its own.
Copying owners that copy like values, step 5 of 5. Showing p, q, Style, Style, s, t, Circle, Circle. In focus: p, s.ownsownsownsownspstd::indirect<Style>qcopy of pStyleowned by pStyleowned by qsstd::polymorphic<Shape>tcopy of sCirclederived from ShapeCirclea Circle again

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. An indirect owner. std::indirect<Style> holds one Style on the heap, an object of a type known in advance.
  2. Copying it copies the Style. Copying p copies the Style it owns, and q owns that new Style: the two never share one.
  3. A polymorphic owner. std::polymorphic<Shape> holds an object of any class derived from Shape, here a Circle.
  4. The copy is a Circle too. Copying s copies the derived object, so t owns a Circle, not just the Shape part of one.
  5. Where they exist. Both types are C++26. GCC 16's standard library ships them, while Apple clang 21's libc++ does not have them yet.

Key idea

std::indirect and std::polymorphic make a deep copy: A copy that duplicates the resource itself, as a copy constructor does when it allocates a new array and copies the values into it, so the two objects own separate resources. More in “The rule of zero, three and five in C++” of the object they own whenever they are copied, so a class built from them copies like a value.

Checkpoint

Check: C++26 owners that copy like values

  1. Question 1 of 2 What does copying a std::indirect<Style> do?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It copies the Style it owns

    A class with a std::indirect member copies like a plain value.

  2. Question 2 of 2 Where were std::indirect and std::polymorphic available for this lesson's programs?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: In GCC 16's standard library, but not yet in Apple clang 21's libc++

    Check the standard library you build with before you rely on them.

Practise

The weakest idea comes first.

Practice that adapts to you

Practise this lesson

14 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 14 draw() renders a Shape during the call and keeps nothing. Which declaration fits?

    Choose one answer.

    Show the answer: Question 1 of 14

    Answer: void draw(const Shape& s);

    A function that only uses an object takes a reference (F.7). A smart pointer parameter would force every caller to own the shape in that particular way, and passing a shared_ptr also has a run-time cost.

  2. Question 2 of 14 sync() writes progress to a Logger if the caller has one, during the call only. Which declaration fits?

    Choose one answer.

    Show the answer: Question 2 of 14

    Answer: void sync(Logger* log);

    The logger is optional and only used during the call, so a raw pointer that may be nullptr fits (F.60). A reference cannot be absent, and smart pointers would change who owns the logger.

  3. Question 3 of 14 find() looks up a customer that a registry owns; there may be no such customer. Which declaration fits?

    Choose one answer.

    Show the answer: Question 3 of 14

    Answer: const Customer* find(std::string_view phone) const;

    A raw pointer points into something another object owns (F.42) and can be nullptr for "not found" (F.60). A reference cannot say "not found", and a smart pointer would hand ownership of the registry's customer to the caller.

  4. Question 4 of 14 A cache keeps an Image for later while the caller also goes on using it. Which declaration fits put()?

    Choose one answer.

    Show the answer: Question 4 of 14

    Answer: void put(std::string key, std::shared_ptr<const Image> image);

    Two parties keep the image, so ownership is shared and the cache takes a shared_ptr by value (R.34), then moves it into its map. const says that nobody changes the image through the cache.

  5. Question 5 of 14 reset_to_default() replaces the caller's Widget with a new default one. Which declaration fits?

    Choose one answer.

    Show the answer: Question 5 of 14

    Answer: void reset_to_default(std::unique_ptr<Widget>& w);

    A reference to a unique_ptr says that the function may make the caller's pointer own a different object, which is what reseating means (R.33). By value, the new widget would be destroyed with the parameter.

  6. Question 6 of 14 schedule() queues work that runs later on another thread and needs a Config until then. Which declaration fits?

    Choose one answer.

    Show the answer: Question 6 of 14

    Answer: void schedule(std::shared_ptr<const Config> config);

    The work outlives the call, so it must keep the Config alive: schedule() takes a share by value (R.34) and moves it into the queued task. A reference or a raw pointer would dangle if the caller's Config went first.

  7. Question 7 of 14 What does void submit(std::unique_ptr<Job>&& job) tell a caller who writes submit(std::move(j))?

    Choose one answer.

    Show the answer: Question 7 of 14

    Answer: submit may or may not take the job, so j must be checked afterwards

    std::move only casts, so a && parameter is a reference until the body moves from it. That is why the guidelines prefer taking a unique_ptr by value for a sink: the caller is then always left empty.

  8. Question 8 of 14 A function only reads the Widget it is given. Its declaration is void show(const std::shared_ptr<Widget>& w). What is the better declaration?

    Choose one answer.

    Show the answer: Question 8 of 14

    Answer: void show(const Widget& w);

    A const reference to a shared_ptr says the function might keep a share (R.36). If it never does, a reference to the Widget says exactly what happens and also works for callers that do not hold the widget in a shared_ptr.

  9. Question 9 of 14 make_shape() creates a Shape from a name read from a file. What should it return?

    Choose one answer.

    Show the answer: Question 9 of 14

    Answer: std::unique_ptr<Shape>

    A factory returns a unique_ptr (F.26). Callers who want to share the shape can convert it to a shared_ptr in one step; nothing converts a shared_ptr back into a unique_ptr, and a raw pointer would leave the delete to comments.

  10. Question 10 of 14 A garage keeps a parked Vehicle until the garage itself is destroyed, and nobody else needs it. Which declaration fits park()?

    Choose one answer.

    Show the answer: Question 10 of 14

    Answer: void park(std::unique_ptr<Vehicle> v);

    Taking a unique_ptr by value says that park() takes ownership (R.32), and the caller has to write std::move, so the transfer shows at the call. A raw pointer or a reference never transfers ownership (I.11), and a shared_ptr would claim that others keep a share.

  11. Question 11 of 14 A factory keeps every object it hands out, so that it can hand the same object out again later. What should it return?

    Choose one answer.

    Show the answer: Question 11 of 14

    Answer: std::shared_ptr to the object

    A factory returns std::shared_ptr only when it keeps a share itself, for example a cache that tracks the objects it hands out; otherwise it returns std::unique_ptr.

  12. Question 12 of 14 Which C++26 type holds an object of any class derived from a base, and copies the derived object when it is copied?

    Choose one answer.

    Show the answer: Question 12 of 14

    Answer: std::polymorphic

    std::indirect is for one known type, std::polymorphic for a class hierarchy.

  13. Question 13 of 14 A Report holds its Theme on the heap, and copies of a report must never share a Theme. With GCC 16 in C++26 mode, which member fits?

    Choose one answer.

    Show the answer: Question 13 of 14

    Answer: std::indirect<Theme>

    Some classes need a heap-allocated part yet should copy like plain values; std::indirect is the C++26 owner for one known type.

  14. Question 14 of 14 A Canvas holds a std::vector<std::polymorphic<Shape>> with a Circle in it. What does copying the canvas do with the Circle?

    Choose one answer.

    Show the answer: Question 14 of 14

    Answer: It copies the Circle, as a Circle

    A container of std::polymorphic copies like a container of values, with no clone() to write.

Where each signature goes wrong: three failures reproduced Pro: opens with a Pro, Premium or Ultimate pass

Each signature in the table fails in its own way when it is chosen wrongly or used carelessly. This part reproduces three of those failures with both compilers and ends with a refactoring exercise.

In this part:

  • A reference whose owner the call resets
  • By value or by &&: who owns a refused job
  • What a shared_ptr parameter costs, counted
  • Exercise: refactor a scheduler’s signatures

1 diagram · 8 runnable examples (C header, C++, Python, check.h, scheduler_test.cpp, starter/scheduler.h) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Owners that copy deeply: std::indirect and std::polymorphic on GCC 16 Pro: opens with a Pro, Premium or Ultimate pass

A class with a heap-allocated member must decide what copying it means. This part runs the usual choices side by side, then the two C++26 types that make a deep copy the default, with what to write where they are missing.

In this part:

  • Copying a class with a smart-pointer member: shared state, or no copy at all
  • std::indirect: one heap part that copies and stays const
  • std::polymorphic: copying a class hierarchy without clone()
  • Using them today, and what to write where the library lacks them

1 diagram · 2 runnable examples (Python, const_member.cpp, members.cpp, shapes.cpp) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    In modern C++, what does a raw pointer parameter such as void draw(const Shape* s) tell the caller, and when would you take a reference instead?

    That draw() uses the shape during the call and does not own it: it will not delete it and, unless its documentation says otherwise, will not keep the pointer after it returns. In code that follows the C++ Core Guidelines, raw pointers and references never own (R.3, R.4, I.11); ownership is written with std::unique_ptr or std::shared_ptr. A pointer says one thing a reference cannot: that nullptr, "no shape", is a valid argument, which the function has to handle. When the argument is required, take const Shape& instead: it cannot be null, needs no check and accepts any shape, whatever owns it. A pointer is still the type for a non-owning member, because a reference member cannot be re-pointed.

  2. Mid to senior

    A GUI framework calls on_click(const Screen& s) with *app.current, where current is a std::shared_ptr<Screen> member. Handlers may navigate, which assigns a new screen to current. Crashes are rare, and only some builds show them. Explain the bug, how to prove it, and where the fix belongs.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Senior to staff

    A profile of a busy service shows about 6 % of CPU in atomic increments and decrements, under 400 functions that take std::shared_ptr<const Config> by value. Which signatures do you change, to what, which must stay, and how do you change them safely?

    Model answer, Pro. First step and follow-ups

  4. Fresher to mid

    A library's factory returns std::shared_ptr<Parser>. One team wants to keep parsers in a std::vector<std::unique_ptr<Parser>> and cannot. What should the factory return, why does the change cost callers almost nothing, and when would returning shared_ptr be right?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • References and raw pointers lend: the function uses the object during the call and never deletes it.
  • Take std::unique_ptr by value to take ownership, and std::shared_ptr by value only to keep a share.
  • Use smart pointers in a signature only when the call changes ownership; otherwise take T& or T*.
  • Factories return std::unique_ptr, which callers can keep, move or turn into a shared_ptr.
  • C++26’s std::indirect and std::polymorphic own a heap part and copy it like a value.

Terms in this lesson

sink parameter
A parameter that takes over the object it is given, such as a std::unique_ptr taken by value: the caller writes std::move and is left empty, and the function keeps or destroys the object.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.