std::unique_ptr: Exclusive Ownership
Learn how std::unique_ptr provides zero-overhead exclusive ownership, how to use make_unique, custom deleters for C resources, and factory function patterns.
What Is unique_ptr?
std::unique_ptreste un pointer inteligent care deține un obiect exclusiv - există exact ununique_ptrcare indică către orice obiect dat. Cândunique_ptreste distrus (iese din domeniul de aplicare), acesta șterge automat obiectul deținut.
Proprietăți cheie:
- Necopiabil — copierea ar crea doi proprietari, încălcând dreptul de proprietate exclusivă.
- Movabil - proprietatea poate fi transferată prinstd::move.
- Zero overhead —sizeof(unique_ptr(cu ștergere implicită). Codul generat este identic cu utilizarea pointerului brut cu ștergere manuală.
- Acesta este indicatorul inteligent implicit. Ajungeți mai întâi launique_ptr; utilizațishared_ptrnumai atunci când aveți nevoie cu adevărat de proprietate comună.
make_unique: Singura cale corectă
std::make_unique(C++14) is the only correct way to create aunique_ptr. It is exception-safe, concise, and avoids writingnew.
#include <iostream>
#include <memory>
#include <string>
struct Connection {
std::string host;
int port;
Connection(std::string h, int p) : host(std::move(h)), port(p) {
std::cout << "Connected to " << host << ":" << port << '\n';
}
~Connection() {
std::cout << "Disconnected from " << host << '\n';
}
void query(const std::string& sql) {
std::cout << "Executing: " << sql << '\n';
}
};
int main() {
// CORRECT: use make_unique
auto conn = std::make_unique<Connection>("localhost", 5432);
conn->query("SELECT * FROM users");
// Access the raw pointer (non-owning)
Connection* raw = conn.get(); // does NOT transfer ownership
// Transfer ownership
auto conn2 = std::move(conn);
// conn is now nullptr
if (!conn) std::cout << "conn is null after move\n";
// conn2 destroyed here — destructor called automatically
} // Output: "Disconnected from localhost"Elemente de ștergere personalizate pentru resursele C
Multe biblioteci C returnează pointeri bruti care trebuie eliberați cu o funcție specifică (nudelete).unique_ptracceptă eliminări personalizate pentru a le gestiona. Deleterul devine parte a tipului, deci are zero overhead atunci când se folosește un pointer de funcție sau lambda fără stat.
#include <cstdio>
#include <memory>
#include <iostream>
// Custom deleter for FILE*
struct FileDeleter {
void operator()(FILE* f) const {
if (f) {
std::fclose(f);
std::cout << "File closed\n";
}
}
};
// Type alias for clarity
using UniqueFile = std::unique_ptr<FILE, FileDeleter>;
// Factory function
UniqueFile open_file(const char* path, const char* mode) {
FILE* f = std::fopen(path, mode);
if (!f) throw std::runtime_error("Cannot open file");
return UniqueFile(f);
}
int main() {
try {
auto file = open_file("/tmp/test.txt", "w");
std::fputs("Hello, RAII!\n", file.get());
// file automatically closed when scope exits
} catch (const std::exception& e) {
std::cerr << e.what() << '\n';
}
// Lambda deleter (less common, but useful for one-offs)
auto deleter = [](int* p) { std::cout << "custom free\n"; delete p; };
std::unique_ptr<int, decltype(deleter)> p(new int(42), deleter);
}unique_ptr cu Arrays
std::unique_ptrgestionează o matrice alocată dinamic și apelează automatdelete[]. Oferăoperator[]în loc deoperator*șioperator->.
auto arr = std::make_unique<int[]>(100); // 100 zero-initialized ints
arr[0] = 42;However, std::vector is almost always preferable — it knows its size, supports iteration, and is just as efficient. Use unique_ptr numai atunci când interfațați cu API-urile C sau când aveți nevoie de un buffer neredimensionabil, care nu poate fi copiat.
Funcții din fabrică Se returnează unique_ptr
Revenireaunique_ptrdin funcțiile din fabrică este modelul idiomatic de proprietate C++. Apelantul primește proprietatea exclusivă. Dacă apelantul dorește o proprietate comună, poate converti:std::shared_ptr.
#include <memory>
#include <iostream>
#include <string>
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
virtual std::string name() const = 0;
};
class Circle : public Shape {
double radius_;
public:
explicit Circle(double r) : radius_(r) {}
double area() const override { return 3.14159 * radius_ * radius_; }
std::string name() const override { return "Circle"; }
};
class Rectangle : public Shape {
double w_, h_;
public:
Rectangle(double w, double h) : w_(w), h_(h) {}
double area() const override { return w_ * h_; }
std::string name() const override { return "Rectangle"; }
};
// Factory — caller owns the result
std::unique_ptr<Shape> make_shape(const std::string& type, double a, double b = 0) {
if (type == "circle") return std::make_unique<Circle>(a);
if (type == "rectangle") return std::make_unique<Rectangle>(a, b);
return nullptr; // unknown shape
}
int main() {
auto s = make_shape("circle", 5.0);
if (s) {
std::cout << s->name() << " area: " << s->area() << '\n';
}
// s automatically deleted here
}Nu transmiteți niciodatăunique_ptrprin referință constantă unei funcții care observă doar obiectul - treceți în schimb un pointer sau o referință brută. Parametriiunique_ptrsemnalează transferul proprietății.
-void use(Widget* w)— observă, nicio proprietate
-void sink(std::unique_ptr— preia proprietatea (apelați custd::move)
-void reseat(std::unique_ptr— poate înlocui obiectul deținut
Nu construiți niciodată douăunique_ptrdin același pointer brut - ambele vor apeladelete, provocând dublarea liberă. Acesta este motivul pentru caremake_uniqueeste preferat: nu există un pointer brut care să fie reutilizat accidental.
1. Implicit launique_ptr — ar trebui să fie prima ta alegere pentru alocarea heap.
2. Folosiți întotdeaunamake_unique — nu scrieți niciodatăunique_ptr.
3. Returnunique_ptrdin fabrici — se convertește implicit înshared_ptrdacă apelantul are nevoie de el.
4. Trecere după valoare pentru a transfera proprietatea —void sink(unique_ptr. Apelantul trebuie să foloseascăstd::move.
5. Trimite indicatorul/referința brută pentru a observa —void use(const T& obj)sauvoid use(T* obj).
6. Marcați constructorul/alocarea de mutarenoexcept — critic pentru tipurile stocate în containere.
unique_ptroferă proprietate exclusivă cu zero overhead vs pointers brut- Creați întotdeauna prin
std::make_unique— nu utilizați niciodată(args...) newbrut - Necopiabil, dar mobil - transferuri de proprietate prin
std::move - Ștergerile personalizate activează RAII pentru resursele C (FIȘIER*, socketuri etc.)
- Returnează
unique_ptrdin funcțiile din fabrică - se convertește înshared_ptrdacă este necesar - Treceți indicator sau referință brută atunci când o funcție doar observă, nu deține
Test — Verifică-ți cunoștințele
(15 XP)1. Care este costul general al`std::unique_ptr<T>`în comparație cu un`T*`brut (cu ștergere implicită)?
2. Cum ar trebui să transmiteți un`unique_ptr`unei funcții care trebuie să preia proprietatea?
3. De ce este`std::make_unique`preferat față de`unique_ptr<T>(new T(...))`?