std::unique_ptr: Výhradní vlastnictví
Zjistěte, jak std::unique_ptr poskytuje výhradní vlastnictví s nulovou režií, jak používat make_unique, vlastní odstraňovače pro prostředky C a vzory továrních funkcí.
Co je unique_ptr?
std::unique_ptrje inteligentní ukazatel, který vlastní výhradně objekt – existuje přesně jedenunique_ptrukazující na jakýkoli daný objekt. Když jeunique_ptrzničen (přejde mimo rozsah), automaticky odstraní vlastněný objekt.
Klíčové vlastnosti:
- Nekopírovatelné – kopírování by vytvořilo dva vlastníky a porušilo výhradní vlastnictví.
- Moveable — vlastnictví lze převést prostřednictvímstd::move.
- Nulová režie —sizeof(unique_ptr(s výchozím odstraňovačem). Vygenerovaný kód je identický s použitím nezpracovaného ukazatele s ručním odstraněním.
- Toto je výchozí inteligentní ukazatel. Nejprve sáhněte pounique_ptr;shared_ptrpoužívejte pouze tehdy, když skutečně potřebujete sdílené vlastnictví.
make_unique: Jediný správný způsob
std::make_unique(C++14) je jediný správný způsob, jak vytvořitunique_ptr. Je bezpečný pro výjimky, stručný a vyhýbá se psanínew.
#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"Vlastní Deleters pro C Resources
Mnoho knihoven C vrací nezpracované ukazatele, které musí být uvolněny pomocí konkrétní funkce (nikolidelete).unique_ptrpodporuje vlastní mazače, aby je zvládly. Odstraňovač se stává součástí typu, takže při použití ukazatele funkce nebo bezstavové lambdy má nulovou režii.
#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 s poli
std::unique_ptrspravuje dynamicky alokované pole a automaticky voládelete[]. Poskytujeoperator[]místooperator*aoperator->.
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 pouze při propojení s C API nebo když potřebujete nezměnit velikost a nekopírovat vyrovnávací paměť.
Tovární funkce Vrací unique_ptr
Návratunique_ptrz továrních funkcí je idiomatickým vzorem vlastnictví C++. Volající získává výhradní vlastnictví. Pokud chce volající sdílet vlastnictví, může převést: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
}Nikdy nepředávejteunique_ptrpomocí const reference do funkce, která pouze pozoruje objekt – místo toho předejte nezpracovaný ukazatel nebo odkaz. Parametryunique_ptrsignalizují převod vlastnictví.
-void use(Widget* w)— pozoruje, žádné vlastnictví
-void sink(std::unique_ptr— převezme vlastnictví (zavolejte sstd::move)
-void reseat(std::unique_ptr— může nahradit vlastněný objekt
Nikdy nevytvářejte dvaunique_ptrze stejného nezpracovaného ukazatele – oba budou volatdelete, což způsobí dvojité uvolnění. To je důvod, proč je preferovánmake_unique: neexistuje žádný nezpracovaný ukazatel, který by bylo možné náhodně znovu použít.
1. Výchozí naunique_ptr — měla by to být vaše první volba pro alokaci haldy.
2. Vždy používejtemake_unique — nikdy nezapisujteunique_ptr.
3. Vraťteunique_ptrz továren — implicitně se převede nashared_ptr, pokud to volající potřebuje.
4. Předáním hodnoty převeďte vlastnictví —void sink(unique_ptr. Volající musí používatstd::move.
5. Předejte nezpracovaný ukazatel/odkaz k pozorování —void use(const T& obj)nebovoid use(T* obj).
6. Označte konstruktor přesunu/přiřazenínoexcept – kritické pro typy uložené v kontejnerech.
unique_ptrprovides exclusive ownership with zero overhead vs raw pointers- Vždy vytvářejte přes
std::make_unique— nikdy nepoužívejte nezpracovaný(args...) new - Nekopírovatelné, ale přenosné – převod vlastnictví prostřednictvím
std::move - Vlastní odstraňovače umožňují RAII pro prostředky C (FILE*, sokety atd.)
- Vraťte
unique_ptrz továrních funkcí – v případě potřeby se převede nashared_ptr - Předat nezpracovaný ukazatel nebo odkaz, když funkce pouze pozoruje, nikoli vlastní
Kvíz — Otestujte své znalosti
(15 XP)1. Jaká je režie`std::unique_ptr<T>`ve srovnání s nezpracovaným`T*`(s výchozím odstraňovačem)?
2. Jak byste měli předat`unique_ptr`funkci, která potřebuje převzít vlastnictví?
3. Proč je`std::make_unique`preferován před`unique_ptr<T>(new T(...))`?