[C++] How to Use std::unique_ptr | Smart Pointers to Automate Memory Management

目次

Introduction

When dynamic memory is allocated using new in C++, it must always be manually released using delete. Forgetting to do so causes memory leaks.

To solve this problem, C++11 introduced std::unique_ptr, a smart pointer that automates object lifetime management based on the RAII (Resource Acquisition Is Initialization) principle.

  • Acquisition: It takes ownership of memory (resource) when the unique_ptr is created.
  • Release: When the unique_ptr goes out of scope and is destroyed, its destructor automatically calls delete to release the resource.

In this article, I will explain three main uses of unique_ptr:

  1. Basic Usage (Automatic Memory Release)
  2. Moving Ownership (std::move)
  3. Specifying Custom Deleters

Prerequisite: What is C++11?

C++11 is a major update to the C++ language formalized in 2011. Since unique_ptr was introduced in C++11, you need a compiler that supports C++11 or later to use it.

1. Basic Usage

A pointer wrapped in unique_ptr is automatically deleted when the unique_ptr variable goes out of scope.

Sample Code

#include <iostream>
#include <memory> // unique_ptr, make_unique
#include <string>

using namespace std;

struct Resource {
    string name;
    Resource(string n) : name(n) { cout << "Resource '" << name << "' acquired" << endl; }
    ~Resource() { cout << "Resource '" << name << "' released" << endl; }
    void use() { cout << "Resource '" << name << "' is being used..." << endl; }
};

void basic_usage_example() {
    cout << "--- Entered function ---" << endl;
    
    // Use make_unique (C++14) to safely create a unique_ptr
    unique_ptr<Resource> p_res = make_unique<Resource>("Data A");
    
    // Access members with -> just like a normal pointer
    p_res->use();

    cout << "--- Exiting function ---" << endl;
    // p_res goes out of scope here, so the destructor is called,
    // and the Resource object is automatically deleted.
}

int main() {
    basic_usage_example();
    return 0;
}

2. Moving Ownership (std::move)

As the name implies, unique_ptr enforces unique (sole) ownership, so copying it is strictly prohibited. If you want to transfer the ownership held by one unique_ptr to another, you must use std::move.

Sample Code

#include <iostream>
#include <memory>
#include <vector>

using namespace std;

struct Resource { /* ... */ };

int main() {
    unique_ptr<Resource> p1 = make_unique<Resource>();
    
    // NG: Copying results in a compilation error!
    // unique_ptr<Resource> p2 = p1;
    
    // OK: Use std::move to "move" ownership from p1 to p2
    unique_ptr<Resource> p2 = move(p1);
    
    // p1 has lost ownership and now points to nothing (nullptr)
    if (!p1) {
        cout << "p1 has lost ownership." << endl;
    }
    
    // It is also possible to move ownership into a vector
    vector<unique_ptr<Resource>> vec;
    vec.push_back(move(p2));
    
    return 0;
}

Explanation: After performing move, the original pointers (p1 or p2) become empty and no longer manage the resource. Ownership is always held by only one unique_ptr.

3. Specifying Custom Deleters

unique_ptr allows you to specify a custom deleter for cases where resources need to be released by methods other than delete (e.g., closing a file opened with fopen using fclose).

Sample Code

#include <iostream>
#include <memory>
#include <cstdio> // FILE, fopen, fclose, fwrite

using namespace std;

// 1. Custom deleter function to fclose FILE*
void file_closer(FILE* fp) {
    if (fp) {
        fclose(fp);
        cout << "Custom Deleter: File closed." << endl;
    }
}

int main() {
    // 2. Specify the deleter type in the second template argument of unique_ptr
    unique_ptr<FILE, decltype(&file_closer)> file_ptr(
        fopen("test.txt", "w"), // Acquire resource
        file_closer             // Pass the function used for release
    );

    if (file_ptr) {
        fwrite("hello", 1, 5, file_ptr.get());
    }

    // When exiting the main function, file_ptr's destructor
    // automatically calls file_closer(fp).
    return 0;
}

Explanation: By passing the type of the deleter (decltype(&file_closer)) as the second template argument of unique_ptr and the deleter function itself as the second argument of the constructor, that function will be called instead of delete when releasing the resource.

Summary

In this article, I explained the basics of safe dynamic memory management using C++11 std::unique_ptr.

  • unique_ptr restricts resource ownership to a single entity.
  • It automatically releases resources when going out of scope, eliminating the risk of forgetting to delete.
  • Ownership cannot be copied; it must be moved using std::move.
  • By specifying a custom deleter, it can handle release processes other than delete.

In situations where new is used, using unique_ptr (or shared_ptr) is generally the most basic and powerful way to prevent memory leaks in modern C++.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次