c++ - هل تستخدم لغة pImpl بالفعل في الممارسة؟




oop pimpl-idiom (8)

أقرأ كتاب "C ++ استثنائية" بواسطة Herb Sutter ، وفي ذلك الكتاب تعلمت عن لغة pImpl. في الأساس ، فإن الفكرة هي إنشاء هيكل للأجسام الخاصة class وتخصيصها ديناميكيًا لتقليل وقت التجميع (وكذلك إخفاء التطبيقات الخاصة بطريقة أفضل).

فمثلا:

class X
{
private:
  C c;
  D d;  
} ;

يمكن تغييرها إلى:

class X
{
private:
  struct XImpl;
  XImpl* pImpl;       
};

و ، في CPP ، التعريف:

struct X::XImpl
{
  C c;
  D d;
};

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

هل يجب علي استخدامه في كل مكان ، أو بحذر؟ وهل هذه التقنية موصى باستخدامها في الأنظمة المدمجة (حيث يكون الأداء مهمًا للغاية)؟


لذلك ، أنا أتساءل أنه يتم استخدام هذه التقنية حقا في الممارسة؟ هل يجب علي استخدامه في كل مكان ، أو بحذر؟

بالطبع يتم استخدامه ، وفي مشروعي ، في كل صف تقريبًا ، لعدة أسباب ذكرتها:

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

هل هذه التقنية موصى باستخدامها في الأنظمة المدمجة (حيث يكون الأداء مهمًا جدًا)؟

هذا يعتمد على مدى قوة هدفك. لكن الجواب الوحيد على هذا السؤال هو: قياس وتقييم ما تكسبه وتخسره.


أعتقد أن هذا هو واحد من أهم الأدوات الأساسية لفك الارتباط.

كنت تستخدم pimpl (والعديد من التعابير الأخرى من C ++ استثنائية) على مشروع مضمن (SetTopBox).

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


اتفق مع جميع الآخرين بشأن البضائع ، لكن دعوني أضع حدا للأدلة: لا يعمل بشكل جيد مع القوالب .

السبب هو أن إنشاء قالب القالب يتطلب الإعلان الكامل المتوفر في المكان الذي حدث فيه التثبيت. (وهذا هو السبب الرئيسي في عدم رؤية طرق القالب محددة في ملفات CPP)

لا يزال بإمكانك الرجوع إلى الفئات الفرعية المعيارية ، ولكن بما أنه يجب عليك تضمينها جميعًا ، يتم فقد كل ميزة "فصل التنفيذ" في التجميع (تجنب تضمين جميع التعليمات البرمجية المحددة في كل مكان ، تقصير التجميع).

هو نموذج جيد ل OOP الكلاسيكي (تستند الميراث) ولكن ليس للبرمجة العامة (على أساس التخصص).


اعتدت على استخدام هذا الأسلوب كثيرًا في الماضي ، لكنني وجدت نفسي أبتعد عنه.

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

مزايا pImpl هي:

  1. بافتراض وجود تنفيذ واحد فقط لهذه الواجهة ، فمن الواضح من خلال عدم استخدام تطبيق الطبقة المجردة / ملموسة

  2. إذا كان لديك مجموعة من الفصول الدراسية (وحدة نمطية) بحيث يصل العديد من الطبقات إلى نفس "impl" ، إلا أن مستخدمي الوحدة الدراسية سيستخدمون الفئات "المكشوفة" فقط.

  3. لا يوجد جدول- v إذا كان هذا يفترض أن يكون أمراً سيئاً.

عيوب وجدت من pImpl (حيث تعمل واجهة مجردة بشكل أفضل)

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

  2. (أكبر قضية). قبل أيام unique_ptr والانتقال كان لديك خيارات محدودة لكيفية تخزين pImpl. مؤشر خام وكان لديك مشكلات في أن تكون صفك غير قابل للنسخ. لن يعمل auto_ptr القديم مع الطبقة المعلنة (ليس على جميع المترجمين على أي حال). لذلك بدأ الناس في استخدام shared_ptr الذي كان لطيفًا في جعل صفك قابلاً للنسخ ولكن بالطبع كانت للنسختين نفس المشاركة الأساسية التي قد لا تتوقعها (تعديل واحد وكلاهما تم تعديلهما). لذا كان الحل في كثير من الأحيان يستخدم المؤشر الخام للداخلية وجعل الصف غير قابل للنسخ وإرجاع common_ptr إلى ذلك بدلاً من ذلك. لذلك يدعو إلى الجديد. (في الواقع 3 أعطيت shared_ptr القديمة لك ثانية واحدة).

  3. من الناحية الفنية لا يتم تصحيحها فعلاً حيث لا يتم نشر constness عبر مؤشر عضو.

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


لقد قدم أشخاص آخرون بالفعل الدعم الفني / السلبي ، ولكني أعتقد أن ما يلي جدير بالملاحظة:

أولا وقبل كل شيء ، لا تكون عقائديا. إذا كان pImpl يعمل على موقفك ، فاستخدمه - لا تستخدمه فقط لأنه "من الأفضل لـ OO لأنه يخفي التنفيذ بالفعل " إلخ. نقلا عن الأسئلة المتداولة في C ++:

التغليف هو رمز ، وليس الناس ( source )

فقط لإعطائك مثالاً على برنامج مفتوح المصدر حيث يتم استخدامه ولماذا: OpenThreads ، مكتبة مؤشر الترابط المستخدمة من قبل OpenSceneGraph . والفكرة الرئيسية هي إزالة <Thread.h> (على سبيل المثال <Thread.h> ) من جميع التعليمات البرمجية الخاصة <Thread.h> الأساسي ، لأن متغيرات الحالة الداخلية (مثل مقابض الخيوط) تختلف من منصة إلى أخرى. وبهذه الطريقة يمكن للمرء أن يقوم بتجميع الكود على مكتبتك دون أي معرفة بالخصائص الأساسية للمنصات الأخرى ، لأن كل شيء مخفي.


هنا سيناريو حقيقي صادفته ، حيث ساعد هذا المصطلح الكثير. لقد قررت مؤخرًا دعم DirectX 11 ، بالإضافة إلى دعم DirectX 9 الحالي ، في محرك ألعاب. المحرك بالفعل ملفوفة معظم ميزات DX ، لذلك تم استخدام أي من واجهات DX مباشرة ؛ تم تعريفهم فقط في الرؤوس كأعضاء خاصين. يستخدم المحرك DLLs كملحقات ، مضيفًا لوحة المفاتيح ، والماوس ، وعصا التحكم ، ودعم البرمجة ، في الأسبوع مثل العديد من الإضافات الأخرى. في حين أن معظم هذه DLLs لم تستخدم DX مباشرة ، فإنها تطلبت المعرفة والربط إلى DX لمجرد أنها سحبت في الرؤوس التي كشفت DX. عند إضافة DX 11 ، كان هذا التعقيد يزداد بشكل كبير ، ولكن بلا داعٍ. نقل أعضاء DX إلى Pimpl المحددة فقط في المصدر ألغى هذا الفرض. علاوةً على هذا الانخفاض في اعتمادات المكتبات ، أصبحت واجهاتي المكشوفة أكثر نظافةً ، حيث تم نقل وظائف عضو خاص إلى Pimpl ، مما أدى إلى تعريض الواجهات الأمامية المواجهة فقط.


يبدو أن الكثير من المكتبات هناك تستخدمه لتبقى مستقرة في API الخاصة بهم ، على الأقل لبعض الإصدارات.

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

المزايا التي قد تمنحها لك هي:

  • يساعد في الحفاظ على التوافق الثنائي بين المكتبات المشتركة
  • إخفاء بعض التفاصيل الداخلية
  • انخفاض دورات recompilation

قد تكون أو لا تكون مزايا حقيقية لك. مثل بالنسبة لي ، لا يهمني بضع دقائق recompilation. عادةً ما لا يقوم المستخدمون النهائيون بذلك ، حيث يقومون دائمًا بتجميعه مرة واحدة ومن البداية.

العيوب المحتملة (هنا أيضًا ، اعتمادًا على التنفيذ وما إذا كانت عيوبًا حقيقية لك):

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

لذا اعطي كل شيء قيمة بعناية ، وقم بتقييمه لنفسك. بالنسبة لي ، يكاد يكون دائما أن استخدام لغة pimpl لا يستحق كل هذا الجهد. هناك حالة واحدة فقط يمكنني استخدامها شخصيا (أو على الأقل شيء مماثل):

غلاف C ++ الخاص بي لاستدعاء stat . هنا قد يختلف البنية من الرأس C ، وفقًا لما تم تعيينه #defines . وبما أن رأس المجمّع الخاص بي لا يمكنه التحكم في كل منها ، فأنا أقوم فقط #include <sys/stat.h> في ملفي .cxx وتجنب حدوث هذه المشكلات.


يتم استخدامه في الممارسة في الكثير من المشاريع. انها usefullness يعتمد بشكل كبير على نوع المشروع. واحد من أبرز المشاريع التي تستخدم هذا هو Qt ، حيث الفكرة الأساسية هي إخفاء التنفيذ أو رمز النظام الأساسي من المستخدم (مطورين آخرين يستخدمون كيو تي).

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

لذلك كما هو الحال في جميع قرارات التصميم تقريبا هناك إيجابيات وسلبيات.







pimpl-idiom