إعادة التوجيه التامة (Perfect Forwarding)
الدوالّ المُنتِجة (Factory functions)
لنفترض أنّنا نرغب في كتابة دالّة منتِجة تقبل قائمة عشوائية من الوسائط، ثمّ تمرّر تلك الوسائط دون تعديل إلى دالّة أخرى. إن دالة make_unique
هي مثال على مثل هذه الدوال، وتُستخدَم لاستنساخ نسخة جديدة من T
بأمان وإعادة مؤشّر فريد unique_ptr<T>
يملك تلك النُسخة.
وتسمح لنا القواعد المتعلقة بالقوالب المتغيّرة (variadic templates) والمراجع اليمينية بكتابة مثل هذه الدالّة.
template < class T, class...A > unique_ptr<T> make_unique(A&&... args) { return unique_ptr<T> (new T(std::forward<A> (args)...)); }
يشير استخدام علامات الحذف ...
إلى حزمة معاملات تمثّل عددًا عشوائيًا من الأنواع، وسينشر المُصرّف تلك الحزمة إلى العدد الصحيح من الوسائط في موضع الاستدعاء، ثم تُمرَّر تلك الوسائط إلى منشئ T
باستخدام std::forward
. ويُطلَب من هذه الدالّة المحافظة على المؤهّلات المرجعية (ref-qualifiers) للوسائط.
struct foo { foo() {} foo(const foo&) {} // مُنشئ النسخ foo(foo&&) {} // مُنشئ النسخ foo(int, int, int) {} }; foo f; auto p1 = make_unique<foo> (f); // foo::foo(const foo&) استدعاء auto p2 = make_unique<foo> (std::move(f)); // foo::foo(foo&&) استدعاء auto p3 = make_unique<foo> (1, 2, 3);
تقنية مؤشر إلى تنفيذ (Pimpl)
الإصدار ≥ C++ 11
في ملف الترويسة:
// widget.h #include <memory> // std::unique_ptr #include <experimental/propagate_const> class Widget { public: Widget(); ~Widget(); void DoSomething(); private: struct Impl; // تصريح لاحق std::experimental::propagate_const<std::unique_ptr < Impl>> pImpl; };
في ملف التنفيذ:
// widget.cpp #include "widget.h" #include "reallycomplextype.h" // widget.h لا حاجة لتضمين هذه الترويسة في struct Widget::Impl { // widget هنا توضع السمات التي نحتاجها من ReallyComplexType rct; }; Widget::Widget(): pImpl(std::make_unique<Impl> ()) {} Widget::~Widget() = default; void Widget::DoSomething() { // pImpl افعل شيئا هنا بـ }
تحتوي pImpl
على حالة Widget
(أو بعضها)، ويمكن تجنّب كشف وصف Widget
في الترويسة، وجعله داخل التنفيذ.
pImpl
هي اختصار لـ "pointer to implementation" (مؤشّر إلى تنفيذ)، أما التنفيذ "الحقيقي" لـ Widget
موجود في pImpl
.
تنبيه: لاحظ أنّه لكي يعمل هذا مع مؤشّر حصري (unique_ptr
)، يجب تنفيذ ~Widget()
في موضع من الملف حيث يكون Impl
مرئيًا بالكامل، يمكنك تحديد الإعداد الافتراضي هناك، لكن إن حدّدت الإعداد الافتراضي في موضع لم تكن فيه Impl
مُعرّفة، فقد يؤدّي ذلك إلى عطب في البرنامج.
التعبيرات المطوية (Fold Expressions)
الطي الأحادي (Unary Folds)
يُستخدَم الطيّ الأحادي (Unary fold) لطيّ حزم المعامِلات (parameter packs) الخاصّة بعامل (operator) محدّد، وهناك نوعان من معاملات الطيّ الأحادية:
-
الطي الأحادي اليساري - Unary Left Fold -
(... op pack)
، والذي يُوسَّع على النحو التالي:
((Pack1 op Pack2) op...) op PackN
*الطي الأحادي اليميني - Unary Right Fold - (pack op ...)
، والذي يُوسَّع كما يلي:
Pack1 op(...(Pack(N - 1) op PackN))
انظر المثال التالي
template < typename...Ts > int sum(Ts...args) { return (...+args); // طيّ أحادي يساري //return (args + ...); // طيّ أحادي يمينيّ // associative سيكونان متكافئين إن كان المعامل تجميعيًا // For +, ((1+2)+3) (left fold) == (1+(2+3)) (right fold) // For -, ((1-2)-3) (left fold) != (1-(2-3)) (right fold) } int result = sum(1, 2, 3); // 6
الطيّ الثنائي أو البتي (Binary Fold)
الطيّ الثنائي، أو البتّي (Binary Fold) هو طيّ أحادي بالأساس، لكن مع وسيط إضافي. وينقسم إلى نوعين:
-
الطيات البتية اليسارية - Binary Left Fold -
(value op ... op pack)
، والتي تُوسَّع على النحو التالي:
(((Value op Pack1) op Pack2) op...) op PackN
-
الطيات البتّية اليمينية (Binary Right Folds)
(pack op ... op value)
، والتي تُوسَّع كما يلي:
Pack1 op(...op(Pack(N - 1) op(PackN op Value)))
انظر المثال التالي:
template < typename...Ts > int removeFrom(int num, Ts...args) { return (num - ...-args); // طية يسرى ثنائية // لاحظ أنّه لا يمكن استخدام عامل طيّ ثنائي يميني // نظرًا لأنّ العامل غير تجميعي } int result = removeFrom(1000, 5, 10, 15); // => 1000 - 5 - 10 - 15 = 970
طيّ الفاصلة (Folding over a comma)
قد ترغب أحيانًا في تطبيق دالّة معيّنة على كل عنصر من عناصر حزمة من المُعاملات. وأفضل حلّ لذلك في C++ 11 هو:
template < class...Ts > void print_all(std::ostream& os, Ts const&... args) { using expander = int[]; (void) expander { 0, (void(os << args), 0)... }; }
ويصبح الأمر أسهل مع طي التعبيرات بحيث لا نحتاج إلى الشيفرات المتداولة (boilerplates) المبهمة، انظر:
template < class...Ts > void print_all(std::ostream& os, Ts const&... args) { (void(os << args), ...); }
ترجمة -بتصرّف- للفصول Chapter 101: Perfect Forwarding و Chapter 107: Pimpl Idiom و Chapter 110: Fold Expressions من كتاب C++ Notes for Professionals
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.