سؤال

كنت أتساءل ما من شأنه أن يجعل مبرمج أن تختار إما Pimpl لغة أو ظاهرية خالصة الدرجة والميراث.

أنا أفهم أن pimpl المصطلح يأتي مع واحدة صريحة إضافية المراوغة لكل الجمهور طريقة إنشاء كائن النفقات العامة.

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

ما يجعل ظاهرية خالصة الدرجة أقل من المرغوب فيه من pimpl المصطلح ؟

هل كانت مفيدة؟

المحلول

عند كتابة فئة C++, أنه من المناسب أن نفكر في ما إذا كان سيكون

  1. نوع قيمة

    نسخ من قيمة الهوية لم الهامة.انه من المناسب أن يكون المفتاح في std::خريطة.سبيل المثال ، "السلسلة" الطبقة ، أو "التاريخ" الطبقة ، أو "مجمع رقم" الطبقة.إلى "نسخ" حالات مثل هذه الدرجة من المنطقي.

  2. كيان نوع

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

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

ومع ذلك, أنا فقط من أي وقت مضى استخدام pImpl لتنفيذ القيمة أنواع (النوع 1) و في بعض الأحيان فقط عندما كنت حقا ترغب في تقليل اقتران وقت التحويل البرمجي التبعيات.في كثير من الأحيان, الأمر لا يستحق عناء.كما أوضحت أن هناك أكثر النحوية العامة لأن لديك لكتابة توجيه طرق جميع الطرق العامة.النوع 2 الطبقات ، أنا دائما استخدام نقية مجردة الفئة الأساسية المرتبطة طريقة مصنع(s).

نصائح أخرى

وPointer to implementation عادة حول إخفاء تفاصيل التنفيذ الهيكلية. Interfaces وعن التوضيح تطبيقات مختلفة. أنها حقا تخدم غرضين مختلفين.

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

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

لقد كان البحث عن إجابة لنفس السؤال.بعد قراءة بعض المقالات وبعض الممارسات أنا أفضل استخدام "ظاهرية خالصة الدرجة واجهات".

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

العيب الوحيد (أنا أحاول أن التحقيق في هذا) هو أن pimpl المصطلح يمكن أن يكون أسرع

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

هناك مشكلة حقيقية جدا مع المكتبات المشتركة التي pimpl لغة تلتف بدقة أن نقي virtuals لا يمكن:لا يمكنك بأمان تعديل/إزالة بيانات أعضاء فئة دون إجبار المستخدمين من فئة إلى إعادة ترجمة التعليمات البرمجية الخاصة بهم.قد يكون مقبولا في ظل بعض الظروف ، ولكن ليس على سبيل المثالنظام المكتبات.

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

// header
struct A
{
public:
  A();
  // more public interface, some of which uses the int below
private:
  int a;
};

// library 
A::A()
  : a(0)
{}

مترجم تنبعث البرمجية في المكتبة المشتركة التي تحسب عنوان صحيح أن تهيئة معين تعويض (ربما الصفر في هذه الحالة, لأنه فقط الأعضاء) من المؤشر إلى كائن فهو يعرف أن this.

على الجانب المستخدم من رمز ، new A أولا تخصيص sizeof(A) بايت من الذاكرة ، ثم يد مؤشر إلى أن الذاكرة إلى A::A() منشئ كما this.

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

قبل pimpl جي, يمكنك بأمان إضافة وإزالة أعضاء البيانات إلى الطبقة الداخلية ، مثل تخصيص الذاكرة و منشئ دعوة يحدث في المكتبة المشتركة:

// header
struct A
{
public:
  A();
  // more public interface, all of which delegates to the impl
private:
  void * impl;
};

// library 
A::A()
  : impl(new A_impl())
{}

كل ما عليك القيام به الآن هو إبقاء الجمهور واجهة مجانية من أعضاء البيانات الأخرى من مؤشر إلى تنفيذ كائن وأنت في مأمن من هذا النوع من الأخطاء.

تحرير: يجب أن ربما أضيف أن السبب الوحيد أنا أتحدث عن منشئ هنا هو أنني لم ترغب في تقديم المزيد من التعليمات البرمجية - نفس الحجج ينطبق على جميع الوظائف التي الوصول إلى بيانات الأعضاء.

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

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

وعلى الرغم من أن يشملها نطاق واسع في إجابات أخرى ربما أنا يمكن أن يكون قليلا أكثر وضوحا بشأن فائدة واحدة من pimpl على فئات أساسية افتراضية:

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

// Pimpl
Object pi_obj(10);
std::cout << pi_obj.SomeFun1();

std::vector<Object> objs;
objs.emplace_back(3);
objs.emplace_back(4);
objs.emplace_back(5);
for (auto& o : objs)
    std::cout << o.SomeFun1();

// Abstract Base Class
auto abc_obj = ObjectABC::CreateObject(20);
std::cout << abc_obj->SomeFun1();

std::vector<std::shared_ptr<ObjectABC>> objs2;
objs2.push_back(ObjectABC::CreateObject(13));
objs2.push_back(ObjectABC::CreateObject(14));
objs2.push_back(ObjectABC::CreateObject(15));
for (auto& o : objs2)
    std::cout << o->SomeFun1();

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

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

والتفاح والبرتقال حقا.

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

وخصوصا "بناء مرات" هي المشكلة التي يمكن أن تحل من قبل الأجهزة أفضل أو باستخدام أدوات مثل Incredibuild (www.incredibuild.com، وشملت أيضا بالفعل في Visual Studio 2017)، وبالتالي لا تؤثر على تصميم البرنامج. تصميم البرمجيات يجب أن تكون مستقلة بشكل عام من الطريقة التي يتم بناء البرنامج.

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