std::unique_ptr: ملكية حصرية
تعرف على كيفية توفير std::unique_ptr للملكية الحصرية بدون أي حمل، وكيفية استخدام make_unique، وأدوات الحذف المخصصة لموارد C، وأنماط وظائف المصنع.
ما هو Unique_ptr؟
std::unique_ptrهو مؤشر ذكي يمتلك كائنًا حصريًا - هناكunique_ptrواحد بالضبط يشير إلى أي كائن معين. عندما يتم تدميرunique_ptr(خارج النطاق)، فإنه يقوم تلقائيًا بحذف الكائن المملوك.
الخصائص الرئيسية:
- غير قابل للنسخ — سيؤدي النسخ إلى إنشاء مالكين، مما ينتهك الملكية الحصرية.
- منقولة — يمكن نقل الملكية عبرstd::move.
- بدون حمل —sizeof(unique_ptr(مع أداة الحذف الافتراضية). الكود الذي تم إنشاؤه مطابق لاستخدام المؤشر الأولي مع الحذف اليدوي.
- هذا هو المؤشر الذكي الافتراضي. الوصول إلىunique_ptrأولاً؛ استخدمshared_ptrفقط عندما تحتاج حقًا إلى ملكية مشتركة.
make_unique: الطريقة الصحيحة الوحيدة
std::make_unique(C++14) هي الطريقة الصحيحة الوحيدة لإنشاءunique_ptr. إنه آمن للاستثناءات وموجز ويتجنب كتابة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"أدوات الحذف المخصصة لموارد C
تُرجع العديد من مكتبات لغة C مؤشرات أولية يجب تحريرها باستخدام وظيفة محددة (وليسdelete). يدعمunique_ptr أدوات الحذف المخصصة للتعامل معها. يصبح أداة الحذف جزءًا من النوع، لذلك لا يوجد أي حمل عند استخدام مؤشر دالة أو لامدا عديمة الحالة.
#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 مع المصفوفات
يديرstd::unique_ptrمصفوفة مخصصة ديناميكيًا ويستدعيdelete[]تلقائيًا. يوفرoperator[]بدلاً منoperator*وoperator->.
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 فقط عند التواصل مع واجهات برمجة التطبيقات C أو عندما تحتاج إلى مخزن مؤقت غير قابل لتغيير الحجم وغير قابل للنسخ.
وظائف المصنع تعود Unique_ptr
إن إرجاعunique_ptrمن وظائف المصنع هو نمط ملكية C++ الاصطلاحي. يحصل المتصل على الملكية الحصرية. إذا كان المتصل يريد ملكية مشتركة، فيمكنه التحويل: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
} لا تقم أبدًا بتمريرunique_ptrبواسطة مرجع ثابت إلى دالة تراقب الكائن فقط - قم بتمرير مؤشر أولي أو مرجع بدلاً من ذلك. تشير معلماتunique_ptrإلى نقل ملكية الإشارة.
-void use(Widget* w)- يلاحظ عدم وجود ملكية
-void sink(std::unique_ptr- يأخذ الملكية (اتصل بـstd::move)
-void reseat(std::unique_ptr— قد يحل محل الكائن المملوك
لا تقم أبدًا بإنشاء اثنين منunique_ptrمن نفس المؤشر الخام - فكلاهما سوف يستدعيdelete، مما يؤدي إلى عدم الازدواج. هذا هو سبب تفضيلmake_unique: لا يوجد مؤشر خام يمكن إعادة استخدامه عن طريق الخطأ.
1. الافتراضي هوunique_ptr — يجب أن يكون خيارك الأول لتخصيص الكومة.
2. استخدم دائمًاmake_unique — لا تكتبunique_ptrأبدًا.
3. إرجاعunique_ptrمن المصانع - يتم تحويله ضمنيًا إلىshared_ptrإذا احتاجه المتصل.
4. التمرير حسب القيمة لنقل الملكية —void sink(unique_ptr. يجب على المتصل استخدامstd::move.
5. قم بتمرير المؤشر/المرجع الخام للملاحظة —void use(const T& obj)أوvoid use(T* obj).
6. وضع علامة على مُنشئ/مهمة النقلnoexcept — أمر بالغ الأهمية للأنواع المخزنة في الحاويات.
- يوفر
unique_ptrملكية حصرية مع صفر حمل مقابل المؤشرات الأولية - قم دائمًا بالإنشاء عبر
std::make_unique- لا تستخدم(args...) newالخام أبدًا - غير قابلة للنسخ ولكنها قابلة للنقل — نقل الملكية عبر
std::move - تعمل أدوات الحذف المخصصة على تمكين RAII لموارد C (ملف*، ومآخذ توصيل، وما إلى ذلك)
- قم بإرجاع
unique_ptrمن وظائف المصنع - فهو يتحول إلىshared_ptrإذا لزم الأمر - قم بتمرير مؤشر أو مرجع أولي عندما تقوم الدالة بالمراقبة فقط، وليس بالملكية
اختبار — اختبر معرفتك
(15 نقطة خبرة)1. ما هو الحمل الزائد لـ`std::unique_ptr<T>`مقارنةً بـ`T*`الخام (مع أداة الحذف الافتراضية)؟
2. How should you pass a`unique_ptr`to a function that needs to take ownership?
3. لماذا يُفضل`std::make_unique`على`unique_ptr<T>(new T(...))`؟