الفرق بين تنفيذ فئة داخل ملف .h أو في ملف .cpp

StackOverflow https://stackoverflow.com/questions/1809679

  •  05-07-2019
  •  | 
  •  

سؤال

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

لشرح ما أتحدث عنه بشكل أفضل ، أعني الاختلافات بين النهج الطبيعي:

// File class.h
class MyClass 
{
private:
  //attributes
  public:
  void method1(...);
  void method2(...);
  ...
};

//file class.cpp
#include "class.h"

void MyClass::method1(...) 
{
  //implementation
}

void MyClass::method2(...) 
{
  //implementation
}

و فقط رئيس يقترب:

// File class.h
class MyClass 
{
private:
  //attributes
public:
  void method1(...) 
  {
      //implementation
  }

  void method2(...) 
  {
    //implementation
  }

  ...
};

يمكنني الحصول على الفرق الرئيسي: في الحالة الثانية ، يتم تضمين الكود في كل ملف آخر يحتاج إلى توليد المزيد من مثيلات من نفس التطبيقات ، وبالتالي التكرار الضمني ؛ بينما يتم تجميع رمز الحالة في حد ذاته ، ثم يتم إحالة كل مكالمة إلى كائن MyClass ترتبط بالتنفيذ في class.cpp.

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

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

المحلول

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

إذا كانت تعريفات وظيفة العضو موجودة في وحدة الترجمة (ملف .cpp) من تلقاء نفسها ، يتم تجميعها مرة واحدة ، وتجميع إعلانات الوظيفة فقط عدة مرات.

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

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

قد تكون قادرًا على رؤية الاختلافات بشكل أفضل من خلال النظر في طريقة محتملة ثالثة للقيام بذلك:

// File class.h
class MyClass
{
    private:
        //attributes
    public:
       void method1(...);
       void method2(...);
       ...
};

inline void MyClass::method1(...)
{
     //implementation
}

inline void MyClass::method2(...)
{
     //implementation
}

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

نصائح أخرى

نعم ، سيحاول المترجم ضمن طريقة تم الإعلان عنها مباشرة في ملف الرأس مثل:

class A
{
 public:
   void method()
   {
   }
};

يمكنني التفكير في اتباع وسائل الراحة في فصل التنفيذ في ملفات الرأس:

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

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

نظرًا لأن الرؤوس تتغير بشكل متكرر من التطبيقات ، من خلال وضع التنفيذ في ملف منفصل ، يمكنك توفير وقت تجميع كبير.

كما أشار بعض الإجابات الأخرى بالفعل ، نعم ، تحديد طريقة داخل ملف ما class سوف تسبب الكتلة المترجم مضمونة.

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

عادة ما يظهر شيء من هذا القبيل في C ++ مع فئات القالب ، حيث يجب تضمين تعريفات أعضاء القالب في ملف الرأس أيضًا (بسبب حقيقة أن معظم المترجمين لا يدعمون export). ولكن مع فصول غير عادية غير موجودة ، لا جدوى من القيام بذلك ، إلا إذا كنت تريد حقًا إعلان أساليبك ك inline.

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

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

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

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