سؤال

ومنذ C ++ يفتقر إلى ميزة interface جاوة وC #، ما هي الطريقة المفضلة لمحاكاة الواجهات في الطبقات C ++؟ تخميني أن يكون وراثة متعددة من فئات مجردة. ما هي الآثار المترتبة من حيث النفقات العامة الذاكرة / الأداء؟ هل هناك أي اتفاقيات التسمية لهذه الواجهات المحاكاة، مثل SerializableInterface؟

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

المحلول

ومنذ C ++ لديها وراثة متعددة على عكس C # وجافا، نعم يمكنك إجراء سلسلة من فئات مجردة.

وأما بالنسبة للاتفاقية، والأمر متروك لكم. ومع ذلك، أود أن تسبق أسماء فئة مع I.

class IStringNotifier
{
public:
  virtual void sendMessage(std::string &strMessage) = 0;
  virtual ~IStringNotifier() { }
};

والأداء هو شيء يدعو للقلق من حيث المقارنة بين C # و Java. في الأساس سيكون لديك فقط من النفقات العامة من وجود جدول بحث عن وظائف بك أو vtable تماما مثل أي نوع من الميراث مع وسائل افتراضية من شأنه أن يعطي.

نصائح أخرى

وليس هناك حقا لا حاجة إلى "محاكاة" أي شيء لأنه ليس من C ++ مفقود أي شيء جافا يمكن القيام به مع واجهات.

ومن وC ++ مؤشر للعرض، جافا يجعل disctinction "مصطنعة" بين interface وclass. وinterface هو مجرد class كل الذين الأساليب هي مجردة والتي لا يمكن أن تحتوي على أي أعضاء البيانات.

وجافا يجعل هذا القيد كما لا يسمح راثة متعددة غير المقيد، ولكنها لا تسمح class إلى implement واجهات متعددة.

في C ++، وclass هو class وinterface هو class. ويتحقق extends بالميراث العام وimplements يتحقق أيضا من خلال الميراث العام.

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

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

و"ما هي الآثار المترتبة من حيث الذاكرة النفقات العامة / الأداء؟"

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

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

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

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

في الأداء، واستدعاء دالة الظاهري لديها اكثر القليل النفقات العامة من استدعاء دالة غير ظاهري، والأهم من ذلك يمكنك أن تفترض أنه عموما (دائما؟) لن inlined. إضافة الفئة الأساسية فارغة عادة لا تضيف أي رمز للبناء أو تدمير، لأن منشئ قاعدة فارغة ومدمر يمكن inlined إلى منشئ الفئة / القانون المدمر مشتقة.

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

وكود مثال:

#include <iostream>

// A is an interface
struct A {
    virtual ~A() {};
    virtual int a(int) = 0;
};

// B is an interface
struct B {
    virtual ~B() {};
    virtual int b(int) = 0;
};

// C has no interfaces, but does have a virtual member function
struct C {
    ~C() {}
    int c;
    virtual int getc(int) { return c; }
};

// D has one interface
struct D : public A {
    ~D() {}
    int d;
    int a(int) { return d; }
};

// E has two interfaces
struct E : public A, public B{
    ~E() {}
    int e;
    int a(int) { return e; }
    int b(int) { return e; }
};

int main() {
    E e; D d; C c;
    std::cout << "A : " << sizeof(A) << "\n";
    std::cout << "B : " << sizeof(B) << "\n";
    std::cout << "C : " << sizeof(C) << "\n";
    std::cout << "D : " << sizeof(D) << "\n";
    std::cout << "E : " << sizeof(E) << "\n";
}

والإخراج (دول مجلس التعاون الخليجي على منصة 32BIT و):

A : 4
B : 4
C : 8
D : 8
E : 12

واجهات في C ++ هي الطبقات التي لها وظائف افتراضية فقط نقية. مثلا :

class ISerializable
{
public:
    virtual ~ISerializable() = 0;
    virtual void  serialize( stream& target ) = 0;
};

وهذه ليست واجهة المحاكاة، بل هو واجهة مثل تلك الموجودة في جافا، ولكن لا يحمل عيوب.

ومنها مثلا. يمكنك إضافة أساليب وأعضاء دون عواقب سلبية:

class ISerializable
{
public:
    virtual ~ISerializable() = 0;
    virtual void  serialize( stream& target ) = 0;
protected:
    void  serialize_atomic( int i, stream& t );
    bool  serialized;
};

لاصطلاحات التسمية ... لا توجد اصطلاحات التسمية الحقيقية المحددة في لغة C ++. حتى اختيار واحد في البيئة الخاصة بك.

والنفقات العامة هو 1 جدول ثابت وفي الفئات المشتقة التي لم يكن لديها حتى الآن الدالات الظاهرية، مؤشر إلى جدول ثابت.

في C ++ يمكننا أن تذهب أبعد من ذلك من واجهات سلوك أقل سهل جافا وشركاه. ويمكننا أن نضيف عقود واضحة (كما في تصميم بالعقود ) مع نمط NVI.

struct Contract1 : noncopyable
{
    virtual ~Contract1();
    Res f(Param p) {
        assert(f_precondition(p) && "C1::f precondition failed");
        const Res r = do_f(p);
        assert(f_postcondition(p,r) && "C1::f postcondition failed");
        return r;
    }
private:
    virtual Res do_f(Param p) = 0;
};

struct Concrete : virtual Contract1, virtual Contract2
{
    ...
};

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

ولكن، إذا كنت تفعل شيئا مثل الخالي الأمثل الدرجة قاعدة، يمكنك تقليل ما يلي:

struct A
{
    void func1() = 0;
};

struct B: A
{
    void func2() = 0;
};

struct C: B
{
    int i;
};

وحجم C سيكون كلمتين.

واجهات في C ++ يمكن أيضا أن تحدث بشكل ثابت، من خلال توثيق المتطلبات على المعلمات نوع القالب.

وقوالب جملة مباراة نمط، لذلك لم يكن لديك لتحديد في خط الهجوم بأن نوعا معينا تنفذ واجهة معينة، طالما أن لديها أعضاء الصحيح. هذا هو على النقيض من <? extends Interface> جافا أو القيود C # الصورة أسلوب where T : IInterface، والتي تتطلب نوع بديلا لمعرفته حول (I) Interface.

وهناك مثال كبير على ذلك هو <م> مكرر الأسرة، والتي يتم تنفيذها من قبل، من بين أمور أخرى، المؤشرات.

وليس هناك طريقة جيدة لتطبيق واجهة الطريق كنت طالبا. المشكلة مع هذا النهج مثل كما مجردة تماما الفئة الأساسية ISerializable تكمن في الطريقة التي C ++ تنفذ وراثة متعددة. مراعاة ما يلي:

class Base
{
};
class ISerializable
{
  public:
    virtual string toSerial() = 0;
    virtual void fromSerial(const string& s) = 0;
};

class Subclass : public Base, public ISerializable
{
};

void someFunc(fstream& out, const ISerializable& o)
{
    out << o.toSerial();
}

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

void fn(Base& b)
{
    cout << (void*)&b << endl;
}
void fn(ISerializable& i)
{
    cout << (void*)&i << endl;
}

void someFunc(Subclass& s)
{
    fn(s);
    fn(s);
}

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

scroll top