هل من الممكن إنشاء فئة فرعية من بنية C في C++ واستخدام مؤشرات للبنية في كود C؟

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

  •  02-07-2019
  •  | 
  •  

سؤال

هل هناك آثار جانبية للقيام بذلك:

كود ج:

struct foo {
      int k;
};

int ret_foo(const struct foo* f){ 
    return f.k; 
}

كود سي++:

class bar : public foo {

   int my_bar() { 
       return ret_foo( (foo)this ); 
   }

};

هناك extern "C" حول كود C++ وكل كود موجود داخل وحدة الترجمة الخاصة به.

هل هذا محمول عبر المترجمين؟

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

المحلول

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

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

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

نصائح أخرى

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

foo* m_pfoo;

في فئة البار وسوف يقوم بنفس المهمة.

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

"لا تستمد من الطبقات الخرسانية." - سوتر

"اجعل فصول غير أوراق مجردة." - مايرز

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

من الناحية الفنية، يمكنك أن تفعل ما تريد، طالما أنك لا تستدعي سلوكًا غير محدد، على سبيل المثال.ز.، حذف مؤشر للفئة المشتقة بواسطة مؤشر إلى كائنها الفرعي للفئة الأساسية.لا تحتاج حتى extern "C" للحصول على كود C++.نعم، إنها محمولة.لكنه تصميم سيء.

وهذا أمر قانوني تمامًا، على الرغم من أنه قد يكون مربكًا للمبرمجين الآخرين.

يمكنك استخدام الميراث لتوسيع بنيات C بالطرق والمنشئات.

عينة :

struct POINT { int x, y; }
class CPoint : POINT
{
public:
    CPoint( int x_, int y_ ) { x = x_; y = y_; }

    const CPoint& operator+=( const POINT& op2 )
    { x += op2.x; y += op2.y; return *this; }

    // etc.
};

قد يكون توسيع البنيات أمرًا "أكثر شرًا"، لكنه ليس شيئًا يُحظر عليك القيام به.

واو، هذا شر.

هل هذا محمول عبر المترجمين؟

بالتأكيد لا.خذ بعين الاعتبار ما يلي:

foo* x = new bar();
delete x;

لكي ينجح هذا، يجب أن يكون foo’s destructor افتراضيًا، ومن الواضح أنه ليس كذلك.طالما أنك لا تستخدم new وطالما أن الكائن المشتق لا يحتوي على أدوات تدمير مخصصة، فقد تكون محظوظًا.

/يحرر:من ناحية أخرى، إذا تم استخدام الكود فقط كما في السؤال، فلن يكون للميراث أي ميزة على التركيب.ما عليك سوى اتباع النصائح المقدمة من m_pGladiator.

يعد هذا أمرًا قانونيًا تمامًا، ويمكنك رؤيته عمليًا مع فئتي MFC CRect وCPoint.CPoint مشتق من POINT (المحدد في Windef.h)، وCRect مشتق من RECT.أنت ببساطة تقوم بتزيين كائن بوظائف الأعضاء.طالما أنك لا تقوم بتوسيع الكائن بمزيد من البيانات، فلا بأس.في الواقع، إذا كان لديك بنية C معقدة يصعب تهيئتها افتراضيًا، فإن توسيعها بفئة تحتوي على مُنشئ افتراضي يعد طريقة سهلة للتعامل مع هذه المشكلة.

حتى لو قمت بذلك:

foo *pFoo = new bar;
delete pFoo;

فأنت بخير، نظرًا لأن المنشئ والمدمر لديك تافهان، ولم تقم بتخصيص أي ذاكرة إضافية.

ليس عليك أيضًا تغليف كائن C++ الخاص بك بـ "C" الخارجي، نظرًا لأنك لا تمرر فعليًا C++ يكتب إلى وظائف C.

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

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

سوف يعمل، ويمكن نقله ولكن لا يمكنك استخدام أي وظائف افتراضية (والتي تشمل المدمرات).

أوصي بدلاً من القيام بذلك أن يكون لديك شريط يحتوي على ملف Foo.

class Bar
{
private:
   Foo  mFoo;
};

لا أفهم سبب عدم قيامك ببساطة بجعل ret_foo طريقة عضو.طريقتك الحالية تجعل من الصعب جدًا فهم التعليمات البرمجية الخاصة بك.ما هو الأمر الصعب جدًا في استخدام فئة حقيقية في المقام الأول مع متغير عضو وأساليب get/set؟

أعلم أنه من الممكن إنشاء بنيات فرعية في لغة C++، ولكن الخطر يكمن في أن الآخرين لن يتمكنوا من فهم ما قمت بترميزه لأنه نادرًا ما يقوم شخص ما بذلك بالفعل.سأختار حلاً قويًا ومشتركًا بدلاً من ذلك.

من المحتمل أن ينجح الأمر ولكني لا أعتقد أنه مضمون.ما يلي هو اقتباس من ISO C++ 10/5:

قد يحتوي الكائن الفرعي للفئة الأساسية على تخطيط (3.7) مختلف عن تخطيط الكائن الأكثر اشتقاقًا من نفس النوع.

من الصعب أن نرى كيف يمكن أن يكون هذا هو الحال في "العالم الحقيقي".

يحرر:

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

يحرر:

هناك طريقة بديلة، وسلوكها محدد جيدًا، وهي جعل 'foo' عضوًا في 'bar' وتوفير عامل تحويل حيثما يكون ذلك ضروريًا.

class bar {
public:    
   int my_bar() { 
       return ret_foo( foo_ ); 
   }

   // 
   // This allows a 'bar' to be used where a 'foo' is expected
   inline operator foo& () {
     return foo_;
   }

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