Skip to content

Smart pointerek

Smart Pointers

One major disadvantage of using pointers is that memory deallocation must be handled by the developer, and failing to free even a single allocation can cause significant problems. Using smart pointers reduces this responsibility, as memory allocation and deallocation happen automatically.

Smart pointers are so-called template classes. This means that when using them, we must specify the type they will manage between the '<' and '>' characters. To use smart pointers, the memory header must be included.

When working with raw pointers, the following operations are the most important: - memory allocation - copying - memory deallocation

With smart pointers, we wrap the pointer itself inside a wrapper class, and by instantiating the wrapper class, we create the pointer. Therefore, these objects should not be considered as dynamically allocating types themselves (like the Course class where we stored Students), but as direct replacements for raw pointer types. These classes implement the * and -> operators, so we can use them with regular pointer syntax.

Memory allocation

When the smart pointer object is instantiated, it is possible to allocate memory.
(A smart pointer can allocate memory in other ways as well, but here we focus only on allocation through the constructor.)
After allocation, the smart pointer continuously manages the memory.

Memory deallocation

Smart pointer objects also have a lifetime, just like any regular object. When an object ceases to exist, it can no longer be referenced. For a raw pointer, this means we must free the allocated memory before the pointer goes out of scope; otherwise, with no remaining references, we won’t know which memory region should be freed.
When a smart pointer object is destroyed, its destructor runs, and the smart pointer automatically frees the allocated memory.

Copying

In the dynamic memory chapter, copying caused most of the difficulties, as we wanted to achieve deep-copy.
When copying a pointer, memory is not allocated by default, since only values are being assigned.
(This is why it is important to emphasize that this is not an object that uses dynamic memory internally, but rather a pointer replacement.)
The copying behavior can be defined using the assignment operator and the copy constructor.

Unique Pointer

Often, we need a memory region to be accessible through only one variable. In such cases, we can use the std::unique_ptr type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <memory>
struct Wrap {
  unsigned i = 0;
  Wrap( unsigned i ) : i( i ) {}
  Wrap() = default;
};

int main() {
  std::unique_ptr<Wrap> ptr( new Wrap( 2 ) ); // memory allocation, value 2 stored in Wrap
  std::unique_ptr<Wrap[]> ptr2( std::make_unique<Wrap[]>( 10 ) ); // memory allocation - 10 elements.

  // copying
  // ptr2 = ptr; // compilation error
  // copying is not allowed

  // object is destroyed.
  // memory does not need to be freed manually.
  // the object's destructor takes care of freeing the memory.
}

As seen in the example above, copying is not allowed with a unique pointer. This guarantees that a single memory address cannot belong to two objects (variables). However, copying can still be required in certain situations — for example, when setting up temporary memory — so this must be handled as well.

In this case, our task was to free the originally allocated memory and update the memory address. What we did not have to worry about was preventing the copied-from element from accessing the memory afterward.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <memory>
#include <iostream>
struct Wrap {
  unsigned i = 0;
  Wrap( unsigned i ) : i( i ) {}
  Wrap() = default;
};

int main() {
  std::unique_ptr<Wrap> ptr( new Wrap( 2 ) ); // memory allocation, value 2 stored in Wrap
  std::unique_ptr<Wrap> ptr2( std::make_unique<Wrap>( 10 ) ); // memory allocation - value 10 stored in Wrap

  std::cout << (ptr2.get() == nullptr) << std::endl; // is the stored pointer null? No.

  // copying (moving)
  ptr = std::move( ptr2 );

  std::cout << (ptr2.get() == nullptr) << std::endl; // is the stored pointer null? Yes, we can no longer access the memory with this.

  // object goes out of scope
  // we do not need to free memory manually
  // the object's destructor handles the deallocation
}

Shared pointer

In the case of a shared pointer, we expect that the same memory address can be referenced by multiple objects (variables). The task to be solved here is ensuring that the memory is freed only when no object (variable) references that memory address anymore. With a shared pointer, this is handled automatically, so we do not need to take care of it ourselves.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <memory>
#include <iostream>
struct Wrap {
  unsigned i = 0;
  Wrap( unsigned i ) : i( i ) {}
  Wrap() = default;
};

void create_and_assign( std::shared_ptr<Wrap>& variable_to_assign ) {
  std::shared_ptr<Wrap> shared1( new Wrap( 10 ) ); // memory allocation
  variable_to_assign = shared1; // pointer copy
  // the first object goes out of scope here
}

int main() {
  std::shared_ptr<Wrap> shared2;
  create_and_assign( shared2 );
  // here, if the first object's destruction had freed the memory, we would get an error
  std::cout << shared2->i << std::endl;
}


Last update: 2025-11-27
Created: 2025-11-27