سؤال

يجب تطبيق C++ التي يمكن أن تكون مبسطة إلى شيء من هذا القبيل:

class AbstractWidget {
 public:
  virtual ~AbstractWidget() {}
  virtual void foo() {}
  virtual void bar() {}
  // (other virtual methods)
};

class WidgetCollection {
 private:
  vector<AbstractWidget*> widgets;

 public:
  void addWidget(AbstractWidget* widget) {
    widgets.push_back(widget);
  }

  void fooAll() {
    for (unsigned int i = 0; i < widgets.size(); i++) {
      widgets[i]->foo();
    }
  }

  void barAll() {
    for (unsigned int i = 0; i < widgets.size(); i++) {
      widgets[i]->bar();
    }
  }

  // (other *All() methods)
};

طلبي هو الأداء الحرجة.هناك عادة الآلاف من الحاجيات في المجموعة.الفئات المشتقة من AbstractWidget (وهناك العشرات) ترك عادة العديد من وظائف افتراضية لا تجاوز.تلك التي تجاوزها عادة ما يكون سريع جدا في التنفيذ.

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

هل هناك أي طريقة لجعل CRTP العمل بالنسبة لي هنا ؟ أو هل هناك أي ذكي حل أي شخص يمكن أن نفكر ؟

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

المحلول

CRTP أو تجميع الوقت تعدد الأشكال عندما تعرف كل أنواع بك في وقت الترجمة.طالما كنت تستخدم addWidget جمع قائمة من الحاجيات في وقت طالما fooAll و barAll ثم تضطر إلى التعامل مع أعضاء متجانسة قائمة الحاجيات في وقت التشغيل ، عليك أن تكون قادرا على التعامل مع أنواع مختلفة في وقت التشغيل.لذلك المشكلة كنت قد قدمت أعتقد أنك تمسك استخدام وقت التشغيل تعدد الأشكال.

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

إذا كنت حقا بحاجة إلى تجنب وقت polymorpism ، ثم واحد من الحلول التالية قد تعمل.

الخيار 1:استخدام الترجمة الوقت مجموعة من الحاجيات

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

template<typename F>
void WidgetCollection(F functor)
{
  functor(widgetA);
  functor(widgetB);
  functor(widgetC);
}

// Make Foo a functor that's specialized as needed, then...

void FooAll()
{
  WidgetCollection(Foo);
}

الخيار 2:محل runtime تعدد الأشكال مع وظائف خالية

class AbstractWidget {
 public:
  virtual AbstractWidget() {}
  // (other virtual methods)
};

class WidgetCollection {
 private:
  vector<AbstractWidget*> defaultFooableWidgets;
  vector<AbstractWidget*> customFooableWidgets1;
  vector<AbstractWidget*> customFooableWidgets2;      

 public:
  void addWidget(AbstractWidget* widget) {
    // decide which FooableWidgets list to push widget onto
  }

  void fooAll() {
    for (unsigned int i = 0; i < defaultFooableWidgets.size(); i++) {
      defaultFoo(defaultFooableWidgets[i]);
    }
    for (unsigned int i = 0; i < customFooableWidgets1.size(); i++) {
      customFoo1(customFooableWidgets1[i]);
    }
    for (unsigned int i = 0; i < customFooableWidgets2.size(); i++) {
      customFoo2(customFooableWidgets2[i]);
    }
  }
};

القبيح, و حقا لا OO.قوالب يمكن أن تساعد مع هذا عن طريق تقليل الحاجة إلى القائمة في كل حال ؛ محاولة شيء من هذا القبيل ما يلي (تماما لم تختبر), لكنك عدت إلى ما لا inlining في هذه الحالة.

class AbstractWidget {
 public:
  virtual AbstractWidget() {}
};

class WidgetCollection {
 private:
  map<void(AbstractWidget*), vector<AbstractWidget*> > fooWidgets;

 public:
  template<typename T>
  void addWidget(T* widget) {
    fooWidgets[TemplateSpecializationFunctionGivingWhichFooToUse<widget>()].push_back(widget);
  }

  void fooAll() {
    for (map<void(AbstractWidget*), vector<AbstractWidget*> >::const_iterator i = fooWidgets.begin(); i != fooWidgets.end(); i++) {
      for (unsigned int j = 0; j < i->second.size(); j++) {
        (*i->first)(i->second[j]);
      }
    }
  }
};

الخيار 3:القضاء OO

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

هذا الحل هو نفس وقت التشغيل تعدد الأشكال, إلا أنه يتطلب الحفاظ على قائمة ثابتة من "الظاهرية" أساليب معروفة فرعية (الذي لا OO) و ذلك يتيح لك استبدال الظاهري المكالمات وظيفة مع طاولة القفز إلى inlined وظائف.

class AbstractWidget {
 public:
  enum WidgetType { CONCRETE_1, CONCRETE_2 };
  WidgetType type;
};

class WidgetCollection {
 private:
  vector<AbstractWidget*> mWidgets;

 public:
  void addWidget(AbstractWidget* widget) {
    widgets.push_back(widget);
  }

  void fooAll() {
    for (unsigned int i = 0; i < widgets.size(); i++) {
      switch(widgets[i]->type) {
        // insert handling (such as calls to inline free functions) here
      }
    }
  }
};

نصائح أخرى

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

وهذا لا يساعد عندما تحتاج حقا وقت تعدد الأشكال، كما هو الحال مثلا عندما يكون لديك وعاء يحتوي على AbstractWidget*، يمكن لكل عنصر أن يكون واحدا من عدة الفئات المشتقة، وعليك أن تكرار عليهم. في CRTP (أو أي رمز قالب)، base<derived1> وbase<derived2> هي الطبقات غير ذات صلة. وبالتالي هكذا هي derived1 وderived2. ليس هناك تعدد الأشكال دينامية بينهما إلا إذا كان لديهم فئة أساسية مشتركة أخرى، ولكن بعد ذلك كنت الى حيث كنت بدأت مع مكالمات افتراضية.

هل يمكن الحصول على بعض تسريع عن طريق استبدال ناقلات مع عدة نواقل: واحد لكل من الفئات المشتقة أن تعرفه عن واحد واحد عام لعند إضافة الفئات المشتقة الجديدة في وقت لاحق وعدم تحديث الحاوية. ثم addWidget يفعل بعض البطيء typeid فحص أو مكالمة افتراضية على أداة لإضافة الأداة إلى الحاوية الصحيحة، وربما لديه بعض الزائدة لحين المتصل يعرف الطبقة وقت التشغيل. يجب الحرص على عدم إضافة قصد فئة فرعية من WidgetIKnowAbout لمكافحة ناقلات WidgetIKnowAbout*. fooAll وbarAll يمكن يتكرر على كل حاوية في صنع بدوره (سريع) تدعو إلى وظائف fooImpl وbarImpl غير الظاهرية التي سيتم بعد ذلك inlined. وبعد ذلك حلقة على ناقلات AbstractWidget* نأمل أصغر بكثير، واصفا foo أو bar الدالات الظاهرية.

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

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

وننظر في الأمر بطريقة أخرى: إذا كان رمز هو أن تكون inlined، وهذا يعني رمز الجهاز الفعلي يجب أن تكون مختلفة لأنواع مختلفة. هذا يعني أنك بحاجة إما حلقات متعددة، أو حلقة مع التحول في ذلك، لأن رمز الجهاز الواضح أنه لا يمكن تغيير في ROM على كل تمر عبر حلقة، وفقا لنوع من بعض مؤشر انسحبت من مجموعة.

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

والجواب باختصار هو لا.

والجواب طويلة (أو campared لا تزال قصيرة إلى بعض الأجوبة الأخرى: -)

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

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

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

والمشكلة أنه سيكون لديك هنا مع WidgetCollection::widgets. يمكن أن تحتوي على ناقل فقط العناصر من نوع واحد، واستخدام CRTP يتطلب أن كل AbstractWidget لها نوع مختلف، templatized حسب نوع مشتق المطلوب. وهذا هو، كنت AbstractWidget أن ننظر بشيء من هذا القبيل:

template< class Derived >
class AbstractWidget {
    ...
    void foo() {
        static_cast< Derived* >( this )->foo_impl();
    }        
    ...
}

وهذا يعني أن كل AbstractWidget مع نوع مختلف Derived سيشكل نوع AbstractWidget< Derived > مختلفة. سوف تخزين كل هذه في ناقل واحد لا يعمل. لذلك يبدو، في هذه الحالة، وظائف الافتراضية هي وسيلة للذهاب.

وليس إذا كنت في حاجة الى ناقلات منهم. الحاويات STL متجانسة تماما، وهو ما يعني أنه إذا كنت بحاجة لتخزين widgetA وwidgetB في نفس الحاوية، ويجب أن تكون موروثة من أحد الوالدين المشترك. وإذا widgetA :: شريط () يفعل شيئا مختلفا عن widgetB :: شريط ()، لديك لجعل وظائف افتراضية.

هل كل من الحاجيات يجب أن تكون في نفس الحاوية؟ هل يمكن أن تفعل شيئا من هذا القبيل

vector<widgetA> widget_a_collection;
vector<widgetB> widget_b_collection;

وبعد ذلك وظائف لا تحتاج إلى أن تكون افتراضية.

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

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

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

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