سؤال

هذا السؤال لديه بالفعل إجابة هنا:

معلومات أساسية:

ال لغة PIMPL (مؤشر التنفيذ) هو أسلوب لإخفاء التنفيذ حيث تقوم الفئة العامة بتغليف بنية أو فئة لا يمكن رؤيتها خارج المكتبة التي تعد الفئة العامة جزءًا منها.

يؤدي هذا إلى إخفاء تفاصيل وبيانات التنفيذ الداخلي عن مستخدم المكتبة.

عند تنفيذ هذا المصطلح، لماذا تضع الأساليب العامة في فئة pimpl وليس الفئة العامة حيث سيتم تجميع تطبيقات أساليب الفئات العامة في المكتبة ويكون لدى المستخدم ملف الرأس فقط؟

للتوضيح، يضع هذا الرمز Purr() التنفيذ على فئة ضمنية ويلتف عليه كذلك.

لماذا لا يتم تطبيق Purr مباشرة على الفصل العام؟

// header file:
class Cat {
    private:
        class CatImpl;  // Not defined here
        CatImpl *cat_;  // Handle

    public:
        Cat();            // Constructor
        ~Cat();           // Destructor
        // Other operations...
        Purr();
};


// CPP file:
#include "cat.h"

class Cat::CatImpl {
    Purr();
...     // The actual implementation can be anything
};

Cat::Cat() {
    cat_ = new CatImpl;
}

Cat::~Cat() {
    delete cat_;
}

Cat::Purr(){ cat_->Purr(); }
CatImpl::Purr(){
   printf("purrrrrr");
}
هل كانت مفيدة؟

المحلول

  • لانك تريد Purr() لتتمكن من استخدام الأعضاء الخاصين CatImpl. Cat::Purr() لن يسمح بمثل هذا الوصول دون friend تصريح.
  • لأنك إذن لا تخلط بين المسؤوليات:فئة واحدة تنفذ، فئة واحدة للأمام.

نصائح أخرى

أعتقد أن معظم الناس يشيرون إلى هذا باسم لغة التعامل مع الجسم.راجع كتاب جيمس كوبلين أساليب وعبارات برمجة C++ المتقدمة (رابط أمازون).ومن المعروف أيضا باسم قط مفترس بسبب شخصية لويس كارول التي تتلاشى حتى تبقى الابتسامة فقط.

يجب توزيع رمز المثال عبر مجموعتين من الملفات المصدر.ثم فقط Cat.h هو الملف الذي يتم شحنه مع المنتج.

يتم تضمين CatImpl.h بواسطة Cat.cpp ويحتوي CatImpl.cpp على تطبيق CatImpl::Purr().لن يكون هذا مرئيًا للعامة الذين يستخدمون منتجك.

الفكرة الأساسية هي إخفاء أكبر قدر ممكن من التنفيذ عن أعين المتطفلين.يكون هذا مفيدًا للغاية عندما يكون لديك منتج تجاري يتم شحنه كسلسلة من المكتبات التي يتم الوصول إليها عبر واجهة برمجة التطبيقات (API) التي يتم تجميع كود العميل عليها وربطها به.

لقد فعلنا ذلك من خلال إعادة كتابة منتج IONAs Orbix 3.3 في عام 2000.

كما ذكر آخرون، فإن استخدام أسلوبه يفصل التنفيذ تمامًا عن واجهة الكائن.إذن لن تضطر إلى إعادة ترجمة كل ما يستخدم Cat إذا كنت تريد فقط تغيير تطبيق Purr().

وتستخدم هذه التقنية في منهجية تسمى التصميم عن طريق العقد.

لما يستحق ذلك، فهو يفصل التنفيذ عن الواجهة.هذا عادة لا يكون مهمًا جدًا في المشاريع الصغيرة الحجم.ولكن، في المشاريع والمكتبات الكبيرة، يمكن استخدامه لتقليل أوقات الإنشاء بشكل كبير.

اعتبر أن تنفيذ Cat قد يتضمن العديد من الرؤوس، وقد يتضمن برمجة تعريفية للقالب والتي تستغرق وقتًا لتجميعها من تلقاء نفسها.لماذا يجب على المستخدم الذي يريد فقط استخدام Cat يجب أن تشمل كل ذلك؟ومن ثم، يتم إخفاء جميع الملفات الضرورية باستخدام مصطلح pimpl (ومن هنا جاء الإعلان الأمامي لـ CatImpl)، واستخدام الواجهة لا يجبر المستخدم على تضمينها.

أقوم بتطوير مكتبة للتحسين غير الخطي (اقرأ "الكثير من الرياضيات السيئة")، والتي يتم تنفيذها في القوالب، لذا فإن معظم التعليمات البرمجية موجودة في الرؤوس.يستغرق التجميع حوالي خمس دقائق (على وحدة معالجة مركزية متعددة النواة)، ومجرد تحليل الرؤوس في ملف فارغ. .cpp يستغرق حوالي دقيقة.لذلك يتعين على أي شخص يستخدم المكتبة الانتظار بضع دقائق في كل مرة يقوم فيها بتجميع التعليمات البرمجية الخاصة به، مما يجعل عملية التطوير هادئة مضجر.ومع ذلك، من خلال إخفاء التنفيذ والرؤوس، يتضمن المرء فقط ملف واجهة بسيط، والذي يتم تجميعه على الفور.

ليس بالضرورة أن يكون لها أي علاقة بحماية التنفيذ من النسخ من قبل شركات أخرى - وهو ما لن يحدث على الأرجح على أي حال، إلا إذا كان من الممكن تخمين الأعمال الداخلية للخوارزمية الخاصة بك من تعريفات متغيرات الأعضاء (إذا كان الأمر كذلك، فمن ربما ليست معقدة للغاية ولا تستحق الحماية في المقام الأول).

إذا كان فصلك يستخدم لغة pimpl، فيمكنك تجنب تغيير ملف الرأس في الفصل العام.

يتيح لك هذا إضافة/إزالة طرق إلى فئة pimpl، دون تعديل ملف رأس الفئة الخارجية.يمكنك أيضًا إضافة/إزالة #التضمينات إلى البثرة أيضًا.

عندما تقوم بتغيير ملف رأس الفئة الخارجية، يجب عليك إعادة ترجمة كل ما يتضمنه (وإذا كان أي من هذه الملفات عبارة عن ملفات رأس، فيجب عليك إعادة ترجمة كل ما يتضمنه، وهكذا)

عادة، المرجع الوحيد لفئة Pimpl في رأس فئة المالك (Cat في هذه الحالة) سيكون إعلانًا للأمام، كما فعلت هنا، لأن ذلك يمكن أن يقلل التبعيات بشكل كبير.

على سبيل المثال، إذا كانت فئة Pimpl الخاصة بك تحتوي على ComplicatedClass كعضو (وليس مجرد مؤشر أو مرجع لها)، فستحتاج إلى تعريف ComplicatedClass بالكامل قبل استخدامه.عمليًا، هذا يعني تضمين "ComplicatedClass.h" (والذي سيتضمن أيضًا بشكل غير مباشر أي شيء يعتمد عليه ComplicatedClass).يمكن أن يؤدي هذا إلى تعبئة رأس واحد يسحب الكثير والكثير من الأشياء، وهو أمر سيئ لإدارة تبعياتك (وأوقات الترجمة الخاصة بك).

عند استخدام معرف pimpl، ما عليك سوى #تضمين العناصر المستخدمة في الواجهة العامة من نوع المالك الخاص بك (والذي سيكون Cat هنا).مما يجعل الأمور أفضل للأشخاص الذين يستخدمون مكتبتك، ويعني أنك لا داعي للقلق بشأن اعتماد الأشخاص على بعض الأجزاء الداخلية من مكتبتك - إما عن طريق الخطأ، أو لأنهم يريدون القيام بشيء لا تسمح به، لذا فهم #يحددون عام خاص قبل تضمين ملفاتك.

إذا كانت فئة بسيطة، فلا يوجد عادةً سبب لاستخدام Pimpl، ولكن في الأوقات التي تكون فيها الأنواع كبيرة جدًا، يمكن أن تكون مساعدة كبيرة (خاصة في تجنب أوقات البناء الطويلة)

حسنًا، لن أستخدمه.لدي بديل أفضل:

فو.ح:

class Foo {
public:
    virtual ~Foo() { }
    virtual void someMethod() = 0;

    // This "replaces" the constructor
    static Foo *create();
}

foo.cpp:

namespace {
    class FooImpl: virtual public Foo {

    public:
        void someMethod() { 
            //....
        }     
    };
}

Foo *Foo::create() {
    return new FooImpl;
}

هل هذا النمط له اسم؟

وباعتباري أيضًا مبرمجًا لـ Python وJava، فإنني أحب هذا أكثر بكثير من لغة pImpl.

نحن نستخدم لغة PIMPL لمحاكاة البرمجة الموجهة نحو الجانب حيث يتم استدعاء الجوانب السابقة واللاحقة والخطأ قبل وبعد تنفيذ وظيفة العضو.

struct Omg{
   void purr(){ cout<< "purr\n"; }
};

struct Lol{
  Omg* omg;
  /*...*/
  void purr(){ try{ pre(); omg-> purr(); post(); }catch(...){ error(); } }
};

نستخدم أيضًا مؤشر الفئة الأساسية لمشاركة الجوانب المختلفة بين العديد من الفئات.

عيب هذا الأسلوب هو أن مستخدم المكتبة يجب أن يأخذ في الاعتبار جميع الجوانب التي سيتم تنفيذها، لكنه يرى فصله فقط.يتطلب تصفح الوثائق بحثًا عن أي آثار جانبية.

إن إجراء استدعاء لـ impl->Purr داخل ملف cpp يعني أنه في المستقبل يمكنك القيام بشيء مختلف تمامًا دون الحاجة إلى تغيير ملف الرأس.ربما يكتشفون في العام المقبل طريقة مساعدة كان بإمكانهم الاتصال بها بدلاً من ذلك، وبالتالي يمكنهم تغيير الرمز لاستدعاء ذلك مباشرة وعدم استخدام impl->Purr على الإطلاق.(نعم، يمكنهم تحقيق نفس الشيء عن طريق تحديث طريقة impl::Purr الفعلية أيضًا، ولكن في هذه الحالة تكون عالقًا في استدعاء دالة إضافية لا يحقق شيئًا سوى استدعاء الوظيفة التالية بدورها)

ويعني أيضًا أن الرأس يحتوي فقط على تعريفات ولا يحتوي على أي تطبيق مما يؤدي إلى فصل أنظف، وهو بيت القصيد من المصطلح.

لقد قمت للتو بتنفيذ أول فصل دراسي للبثور خلال اليومين الماضيين.لقد استخدمته للتخلص من المشكلات التي كنت أواجهها بما في ذلك Winsock2.h في Borland Builder.يبدو أنه كان يفسد محاذاة البنية، وبما أن لدي أشياء مأخذ توصيل في البيانات الخاصة للفصل، كانت هذه المشكلات تنتشر إلى أي ملف cpp يتضمن الرأس.

باستخدام pimpl، تم تضمين Winsock2.h في ملف cpp واحد فقط حيث يمكنني وضع غطاء على المشكلة وعدم القلق من أنها ستعود لتعضني.

للإجابة على السؤال الأصلي، كانت الميزة التي وجدتها في إعادة توجيه الاستدعاءات إلى فئة pimpl هي أن فئة pimpl هي نفسها التي كانت ستكون عليها فئتك الأصلية قبل أن تقوم بتطبيقها، بالإضافة إلى أن تطبيقاتك ليست موزعة على 2 فصول بطريقة غريبة.من الواضح جدًا أن يتم إرسال الجمهور ببساطة إلى فئة البثور.

وكما قال السيد نوديت، فئة واحدة ومسؤولية واحدة.

لا أعرف إذا كان هذا الفرق يستحق الذكر ولكن ...

هل من الممكن أن يكون التنفيذ في مساحة الاسم الخاصة به وأن يكون له مساحة اسم عامة/مكتبة للكود الذي يراه المستخدم:

catlib::Cat::Purr(){ cat_->Purr(); }
cat::Cat::Purr(){
   printf("purrrrrr");
}

بهذه الطريقة، يمكن لجميع أكواد المكتبة الاستفادة من مساحة اسم cat، ومع ظهور الحاجة إلى كشف فئة للمستخدم، يمكن إنشاء غلاف في مساحة اسم catlib.

أجد أنه من الواضح أنه على الرغم من مدى شهرة مصطلح البثور، إلا أنني لا أرى أنه يظهر كثيرًا في الحياة الواقعية (على سبيل المثال:في المشاريع مفتوحة المصدر).

كثيرا ما أتساءل عما إذا كانت "الفوائد" مبالغ فيها؛نعم، يمكنك جعل بعض تفاصيل التنفيذ الخاصة بك مخفية بشكل أكبر، ونعم، يمكنك تغيير التنفيذ الخاص بك دون تغيير الرأس، ولكن ليس من الواضح أن هذه مزايا كبيرة في الواقع.

وهذا يعني أنه ليس من الواضح أن هناك حاجة إلى التنفيذ الخاص بك الذي - التي مخفي جيدًا، وربما يكون من النادر جدًا أن يقوم الأشخاص بتغيير التنفيذ فقط؛بمجرد أن تحتاج إلى إضافة طرق جديدة، على سبيل المثال، تحتاج إلى تغيير الرأس على أي حال.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top