이번 편의 목표: 파일과 메모리의 해제를 객체의 소멸자에 맡기고, 소유권이 하나뿐인 메모리는 unique_ptr로 표현합니다.

RAII는 자원 획득을 초기화에 묶는 방식입니다. 생성자에서 자원을 얻고, 소멸자에서 돌려줍니다. 함수가 중간에 return해도 스택의 객체는 소멸자가 호출됩니다. 13편에서 실패할 때마다 fclose를 적던 일을 타입이 대신합니다.

C++
#include <cstdio>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>

class File {
public:
    explicit File(const char *path) : file_(std::fopen(path, "w")) {
        if (file_ == nullptr) {
            throw std::runtime_error("파일을 열 수 없습니다.");
        }
    }

    ~File() {
        if (file_ != nullptr) {
            std::fclose(file_);
        }
    }

    File(const File &) = delete;
    File &operator=(const File &) = delete;

    void write(const char *text) {
        std::fputs(text, file_);
    }

private:
    std::FILE *file_;
};

struct Book {
    int id;
    std::string title;
};

int main() {
    File note("note.txt");
    note.write("개미\n");

    auto book = std::make_unique<Book>();
    book->id = 1;
    book->title = "개미";
    std::cout << book->title << '\n';
    return 0;
}

File의 복사를 삭제하면 같은 파일 핸들을 가진 객체가 두 개 생기지 않습니다. 핸들을 닫는 소멸자는 하나뿐입니다. main이 끝나면 note의 소멸자가 파일을 닫습니다.

std::unique_ptr은 동적 객체를 하나만 소유합니다. std::make_unique<Book>()이 메모리를 만들고, unique_ptr이 소멸될 때 그 메모리를 돌려줍니다. 복사는 컴파일 오류입니다. 소유권을 넘길 때는 22편의 이동을 사용합니다. 소유하지 않고 들여다보기만 하는 함수에는 Book*나 const Book&를 넘깁니다. 그 포인터로 delete하지 않습니다.

shared_ptr은 소유자를 여럿으로 늘립니다. 진짜로 수명을 공유해야 할 때만 씁니다. 기본 선택은 unique_ptr입니다.

직접 해 보기

Book을 unique_ptr로 하나 더 만들고 제목만 출력한 뒤, 그 포인터를 복사하는 코드가 컴파일되지 않는지 확인하세요.

해설

auto copy = book; 은 unique_ptr의 복사가 막혀 있으므로 컴파일 오류입니다. 두 번째 책은 make_unique로 따로 만듭니다. 각 객체의 메모리는 그 unique_ptr의 수명이 끝날 때 반환됩니다.