اذهب إلى المحتوى

إعادة التوجيه التامة Perfect Forwarding وتقنية مؤشر إلى تنفيذ Pimpl والتعبيرات المطوية Fold Expressions في Cpp


محمد بغات

إعادة التوجيه التامة (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


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...