سؤال

في مكان عملي، نميل إلى الاستخدام com.iostream, خيط, المتجه, خريطة, ، والغريب خوارزمية او اثنين.لم نعثر في الواقع على العديد من المواقف التي كانت فيها تقنيات القالب هي الحل الأفضل لمشكلة ما.

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

كرشوة، توقع التصويت لصالح إجابتك.

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

المحلول

ولقد استعملت الكثير من التعليمات البرمجية القالب، ومعظمها في دفعة وSTL، بل لقد كان نادرا ما حاجة إلى <م> الكتابة أي

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

وبناء على استخدام STL من قالب واحد لدعم كل std::string وstd::wstring، قررت أن محاولة جعل ExeFile قالب، مع هياكل البيانات المختلفة ونوع العنوان كمعلمات. كان هناك اثنين من الأماكن التي لا يزال اضطررت لاستخدام خطوط #ifdef WIN64 (قليلا تجهيز متطلبات مختلفة)، ولكنه لم يكن من الصعب القيام به حقا. لقد حصلنا على الدعم الكامل 32- و 64 بت في هذا البرنامج الآن، واستخدام القالب يعني أن كل تعديل فعلناه منذ تلقائيا ينطبق على كلا الإصدارين.

نصائح أخرى

معلومات عامة عن القوالب:

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

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

الاستخدام الآخر الشائع جدًا هو فرز الخوارزميات.

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

يمكنك تنفيذ دالة Max على أنها ماكرو أو قالب، ولكن تنفيذ القالب سيكون آمنًا وبالتالي أفضل.

والآن إلى الأشياء الرائعة:

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

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

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
    int x = Factorial<4>::value; // == 24
    int y = Factorial<0>::value; // == 1
}

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

ومنشأة واحدة أن تقدم سهرة غير طوابير فاضحة المعاملات، لذلك لدي TpQueue الدرجة التي تتفاعل مع قائمة الانتظار:

class TpQueue {
public:
   void enqueue(...)
   void dequeue(...)
   ...
}

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

class OwnTransaction {
public:
   begin(...)  // Suspend any open transaction and start a new one
   commit(..)  // Commit my transaction and resume any suspended one
   abort(...)
}

class SharedTransaction {
public:
   begin(...)  // Join the currently active transaction or start a new one if there isn't one
   ...
}

والطبقة TpQueue يحصل على إعادة كتابتها ك

template <typename TXNPOLICY = SharedTransaction>
class TpQueue : public TXNPOLICY {
   ...
}

وحتى داخل TpQueue يمكن أن أدعو تبدأ ()، إجهاض ()، ارتكاب () حسب الحاجة ولكن يمكن تغيير السلوك على اساس طريقة أعلن المثيل:

TpQueue<SharedTransaction> queue1 ;
TpQueue<OwnTransaction> queue2 ;

ولقد استخدمت قوالب (مع مساعدة من Boost.Fusion) لتحقيق نوع آمن الأعداد الصحيحة لمكتبة الخط الطبيعي أنني النامية. لدي (فرط) ID حافة وID قمة الرأس وكلاهما صحيحة. مع قوالب، أصبحت قمة الرأس وhyperedge معرفات أنواع مختلفة واستخدام واحد عند الآخر كان من المتوقع ولدت خطأ وقت الترجمة. أنقذني الكثير من الصداع الذي كنت قد خلاف ذلك مع وقت التشغيل التصحيح.

وهنا مثال واحد من مشروع حقيقي. أنا لها وظائف جالبة مثل هذا:

bool getValue(wxString key, wxString& value);
bool getValue(wxString key, int& value);
bool getValue(wxString key, double& value);
bool getValue(wxString key, bool& value);
bool getValue(wxString key, StorageGranularity& value);
bool getValue(wxString key, std::vector<wxString>& value);

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

template <typename T>
T get(wxString key, const T& defaultValue)
{
    T temp;
    if (getValue(key, temp))
        return temp;
    else
        return defaultValue;
}

القوالب التي أستهلكها بانتظام هي العديد من فئات الحاويات، وتعزز المؤشرات الذكية، com.scopeguards, ، عدد قليل من خوارزميات STL.

السيناريوهات التي كتبت فيها القوالب:

  • حاويات مخصصة
  • إدارة الذاكرة وتنفيذ أمان النوع واستدعاء CTor/DTor أعلى مخصصات الفراغ *
  • التنفيذ المشترك للأحمال الزائدة مع أنواع مختلفة، على سبيل المثال.

    Bool Contensnan (float * ، int) bool continsnan (double * ، int)

وكلاهما يستدعي فقط وظيفة مساعد (محلية، مخفية).

template <typename T>
bool ContainsNanT<T>(T * values, int len) { ... actual code goes here } ;

خوارزميات محددة مستقلة عن النوع، طالما أن النوع له خصائص معينة، على سبيل المثال.التسلسل الثنائي.

template <typename T>
void BinStream::Serialize(T & value) { ... }

// to make a type serializable, you need to implement
void SerializeElement(BinStream & strean, Foo & element);
void DeserializeElement(BinStream & stream, Foo & element)

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


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

ونحن نستخدم COM، واستعرض مؤشر إلى كائن إما أن تنفيذ واجهة أخرى مباشرة أو عن طريق [IServiceProvider] (<لأ href = "http://msdn.microsoft.com/en-us/library/cc678965(VS 0.85) .aspx اتصال) "يختلط =" نوفولو noreferrer "> http://msdn.microsoft.com/en-us/library/cc678965 (VS.85) .aspx اتصال) هذا دفعني لإنشاء هذا المساعد يلقي تشبه وظيفة.

// Get interface either via QueryInterface of via QueryService
template <class IFace>
CComPtr<IFace> GetIFace(IUnknown* unk)
{
    CComQIPtr<IFace> ret = unk; // Try QueryInterface
    if (ret == NULL) { // Fallback to QueryService
        if(CComQIPtr<IServiceProvider> ser = unk)
            ser->QueryService(__uuidof(IFace), __uuidof(IFace), (void**)&ret);
    }
    return ret;
}

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

ورأيت مرة واحدة التعليمة البرمجية التالية:

void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
   // three lines of code
   callFunctionGeneric1(c) ;
   // three lines of code
}

وكرر عشر مرات:

void doSomethingGeneric2(SomeClass * c, SomeClass & d)
void doSomethingGeneric3(SomeClass * c, SomeClass & d)
void doSomethingGeneric4(SomeClass * c, SomeClass & d)
// Etc

وكل وظيفة لها نفس 6 خطوط للقانون نسخ / لصق، وفي كل مرة يدعو callFunctionGenericX وظيفة أخرى بنفس احقة الرقم.

وكانت توجد وسيلة لريفاكتور كل شيء تماما. لذا ظللت إعادة بيع ديون محلية.

ولقد غيرت رمز بهذه الطريقة (من الذاكرة):

template<typename T>
void doSomethingGenericAnything(SomeClass * c, SomeClass & d, T t)
{
   // three lines of code
   t(c) ;
   // three lines of code
}

وتعديل التعليمات البرمجية الموجودة:

void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
   doSomethingGenericAnything(c, d, callFunctionGeneric1) ;
}

void doSomethingGeneric2(SomeClass * c, SomeClass & d)
{
   doSomethingGenericAnything(c, d, callFunctionGeneric2) ;
}

والخ.

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

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

template <class Derived>
struct handler_base : Derived {
  void pre_call() {
    // do any universal pre_call handling here
    static_cast<Derived *>(this)->pre_call();
  };

  void post_call(typename Derived::result_type & result) {
    static_cast<Derived *>(this)->post_call(result);
    // do any universal post_call handling here
  };

  typename Derived::result_type
  operator() (typename Derived::arg_pack const & args) {
    pre_call();
    typename Derived::result_type temp = static_cast<Derived *>(this)->eval(args);
    post_call(temp);
    return temp;
  };

و}؛

وشيء من هذا القبيل يمكن استخدامها بعد ذلك للتأكد من معالجات بك تستمد من هذا القالب وإنفاذ تصميم من أعلى إلى أسفل ومن ثم السماح للالتخصيص من أسفل إلى أعلى:

struct my_handler : handler_base<my_handler> {
  typedef int result_type; // required to compile
  typedef tuple<int, int> arg_pack; // required to compile
  void pre_call(); // required to compile
  void post_call(int &); // required to compile
  int eval(arg_pack const &); // required to compile
};

وهذا بعد ذلك يتيح لك الحصول على وظائف متعددة الأشكال العامة التي تتعامل مع handler_base <> أنواع المستمدة فقط:

template <class T, class Arg0, class Arg1>
typename T::result_type
invoke(handler_base<T> & handler, Arg0 const & arg0, Arg1 const & arg1) {
  return handler(make_tuple(arg0, arg1));
};
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top