سؤال

كيف يمكنني إعداد فئة يمثل واجهة ؟ هل هذا مجرد قاعدة مجردة الصف ؟

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

المحلول

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

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Parent
{
    public:
        virtual ~Parent();
};

class Child : public Parent, public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

وليس لديك لتشمل الجسم للالمدمر الظاهري - تبين بعض المجمعين صعوبة في تحسين والمدمر فارغة، وكنت أفضل حالا باستخدام الافتراضي.

نصائح أخرى

تأكد فئة مع أساليب افتراضية خالصة. استخدام واجهة من خلال خلق فئة أخرى أن يتجاوز تلك الأساليب الافتراضية.

وهناك طريقة الظاهري النقي هو أسلوب الفئة التي يتم تعريفها بأنها الظاهري وأسند إلى 0.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

والسبب كله لديك خاصة واجهة نوع فئة بالإضافة إلى فئات قاعدة مجردة في C # / <لأ href = "https://stackoverflow.com/questions/1321122/what-is-an-interface-in- جافا "> جافا لأن C # / جافا لا تدعم وراثة متعددة.

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

وليس هناك مفهوم "واجهة" في حد ذاتها في C ++. AFAIK، أدخلت واجهات لأول مرة في جاوة كمحاولة للتغلب على عدم وجود وراثة متعددة. وقد تحولت هذه الفكرة إلى أن تكون مفيدة للغاية، ونفس التأثير يمكن أن يتحقق في C ++ باستخدام الفئة الأساسية مجردة.

وهناك فئة أساسية مجردة هي فئة التي يعمل فيها عضو واحد على الأقل (طريقة في جاوة لغة) هي دالة ظاهري محض أعلنت باستخدام بناء الجملة التالي:

class A
{
  virtual void foo() = 0;
};

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

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

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

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

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

class IBase {
public:
    virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
    virtual void Describe() = 0; // pure virtual method
};

class Tester : public IBase {
public:
    Tester(std::string name);
    virtual ~Tester();
    virtual void Describe();
private:
    std::string privatename;
};

Tester::Tester(std::string name) {
    std::cout << "Tester constructor" << std::endl;
    this->privatename = name;
}

Tester::~Tester() {
    std::cout << "Tester destructor" << std::endl;
}

void Tester::Describe() {
    std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}


void descriptor(IBase * obj) {
    obj->Describe();
}

int main(int argc, char** argv) {

    std::cout << std::endl << "Tester Testing..." << std::endl;
    Tester * obj1 = new Tester("Declared with Tester");
    descriptor(obj1);
    delete obj1;

    std::cout << std::endl << "IBase Testing..." << std::endl;
    IBase * obj2 = new Tester("Declared with IBase");
    descriptor(obj2);
    delete obj2;

    // this is a bad usage of the object since it is created with "new" but there are no "delete"
    std::cout << std::endl << "Tester not defined..." << std::endl;
    descriptor(new Tester("Not defined"));


    return 0;
}

إذا قمت بتشغيل التعليمات البرمجية السابق دون virtual ~IBase() {};، سترى أن Tester::~Tester() المدمر يسمى أبدا.

جوابي هو أساسا نفس الآخرين ولكن أعتقد أن هناك اثنين من الأشياء الهامة الأخرى القيام به:

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

  2. استخدام الافتراضي الميراث لتجنب المشاكل مثال وراثة متعددة.(هناك في كثير من الأحيان وراثة متعددة عندما نستخدم واجهات.)

ومثل غيرها من الإجابات:

  • جعل الطبقة مع خالص طرق افتراضية.
  • استخدام واجهة طريق إنشاء فئة أخرى أن يتجاوز تلك الظاهري الأساليب.

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
            virtual ~IDemo() {}
    }
    

    أو

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
        protected:
            ~IDemo() {}
    }
    

    و

    class Child : virtual public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    }
    

في C++11 يمكنك بسهولة تجنب الميراث تماما:

struct Interface {
  explicit Interface(SomeType& other)
  : foo([=](){ return other.my_foo(); }), 
    bar([=](){ return other.my_bar(); }), /*...*/ {}
  explicit Interface(SomeOtherType& other)
  : foo([=](){ return other.some_foo(); }), 
    bar([=](){ return other.some_bar(); }), /*...*/ {}
  // you can add more types here...

  // or use a generic constructor:
  template<class T>
  explicit Interface(T& other)
  : foo([=](){ return other.foo(); }), 
    bar([=](){ return other.bar(); }), /*...*/ {}

  const std::function<void(std::string)> foo;
  const std::function<void(std::string)> bar;
  // ...
};

في هذه الحالة, واجهة إشارة دلالات ، أيلديك للتأكد من أن الكائن تعيش أطول واجهة (ومن الممكن أيضا لجعل واجهات مع قيمة دلالات).

هذه النوع من واجهات لها إيجابيات وسلبيات:

  • أنها تتطلب المزيد من الذاكرة من الميراث على أساس تعدد الأشكال.
  • أنها بشكل عام أسرع من الميراث على أساس تعدد الأشكال.
  • في الحالات التي تعرف النهائي النوع ، فهي أسرع بكثير! (بعض المجمعين مثل دول مجلس التعاون الخليجي و رنة إجراء المزيد من التحسينات في الأنواع التي ليس لها/ترث من أنواع وظائف افتراضية).

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

أقول لقد التطبيق الذي أتعامل مع الأشكال polymorphically باستخدام MyShape واجهة:

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle

في التطبيق الخاص بك, يمكنك أن تفعل الشيء نفسه مع الأشكال المختلفة باستخدام YourShape واجهة:

struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...

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

struct Circle : MyShape, YourShape { 
  void my_draw() { /*stays the same*/ };
  void your_draw() { my_draw(); }
};

أولا تعديل بلدي الأشكال قد لا يكون ممكنا على الإطلاق.وعلاوة على ذلك, متعددة الميراث يؤدي الطريق إلى السباغيتي رمز (تخيل الثالث المشروع يأتي في هذا باستخدام TheirShape واجهة...ماذا يحدث إذا كانت أيضا دعوة وظيفة السحب my_draw ?).

تحديث:هناك عدة مراجع جديدة عن غير الميراث على أساس تعدد الأشكال:

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

والخلط؟


    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }

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

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

وReimplementing المدمر في فئة مشتقة وليس صفقة كبيرة على الإطلاق - أنا دائما reimplement المدمر، الظاهري أم لا، في الفئات المشتقة بلدي

.

إذا كنت تستخدم مترجم مايكروسوفت C ++، ثم هل يمكن أن تفعل ما يلي:

struct __declspec(novtable) IFoo
{
    virtual void Bar() = 0;
};

class Child : public IFoo
{
public:
    virtual void Bar() override { /* Do Something */ }
}

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

وثمة إضافة القليل من ما هو مكتوب حتى هناك:

أولا، تأكد المدمر الخاص بك هو أيضا النقي الظاهري

وثانيا، قد ترغب في وراثة تقريبا (وليس عادة) عندما لم تنفذ، لمجرد تدابير جيدة.

ويمكنك أن تنظر أيضا في الطبقات عقد تنفيذه مع NVI (واجهة غير الظاهري نمط). على سبيل المثال:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1();
    void f(Parameters p) {
        assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
        // + class invariants.
        do_f(p);
        // Check post-conditions + class invariants.
    }
private:
    virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
    virtual void do_f(Parameters p); // From contract 1.
    virtual void do_g(Parameters p); // From contract 2.
};

أنا لا تزال جديدة في C++ التنمية.لقد بدأت مع Visual Studio (VS).

حتى الآن لا يبدو أن أحدا ذكر __interface في مقابل (.صافي).أنا لا جدا متأكدا إذا كان هذا هو وسيلة جيدة لإعلان واجهة.ولكن يبدو أن تقديم إضافية إنفاذ (المذكورة في الوثائق).بحيث لا تضطر إلى تحديد صراحة virtual TYPE Method() = 0;, لأنه سيتم تحويلها تلقائيا.

__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};

ومع ذلك, أنا لا أستعمله لأنني قلقها إزاء عبر منصة تجميع التوافق ، لأنه متاح فقط تحت .صافي.

إذا كان أي شخص لديك أي شيء للاهتمام حول هذا الموضوع, يرجى حصة.:-)

شكرا

في حين أنه من الصحيح أن virtual هو بحكم الواقع القياسية لتحديد واجهة, دعونا لا ننسى الكلاسيكية ج-مثل نمط الذي يأتي مع منشئ في C++:

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

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

نصائح:

  • قد ترث من هذا كقاعدة فئة (سواء الافتراضية وغير الافتراضية يسمح) وملء click في سليل منشئ.
  • قد يكون لديك مؤشر وظيفة كما protected الأعضاء ويكون public المرجعية و/أو حاصل.
  • كما ذكر أعلاه, هذا يسمح لك للتبديل التنفيذ في وقت التشغيل.وبالتالي إنها طريقة لإدارة الدولة.اعتمادا على عدد من ifs مقابلالدولة التغييرات في التعليمات البرمجية الخاصة بك ، قد تكون أسرع من switch()أو es ifs (التحول المتوقع حوالي 3-4 ifs, ولكن دائما قياس الأولى.
  • إذا اخترت std::function<> أكثر من مؤشرات الدالة ، قد تكون قادرة على إدارة كل ما تبذلونه من البيانات داخل الكائن IBase.من هذه النقطة, هل يمكن أن يكون قيمة مخططات IBase (على سبيل المثال ، std::vector<IBase> سوف تعمل).علما أن هذا قد يكون أبطأ اعتمادا على مترجم الخاصة بلبنان المدونة ؛ كما أن التطبيقات الحالية std::function<> تميل إلى أن تكون علوية بالمقارنة مع مؤشرات الدالة أو حتى وظائف افتراضية (هذا قد يتغير في المستقبل).

هنا هو تعريف abstract class في c++ القياسية

n4687

13.4.2

فئة مجردة هي الفئة التي يمكن أن تستخدم فقط كقاعدة فئة من فئة أخرى;لا كائنات مجردة فئة يمكن أن تنشأ إلا subobjects من فئة مشتقة من ذلك.فئة مجردة إذا كان لديه على الأقل واحدة وظيفة افتراضية نقية.

class Shape 
{
public:
   // pure virtual function providing interface framework.
   virtual int getArea() = 0;
   void setWidth(int w)
   {
      width = w;
   }
   void setHeight(int h)
   {
      height = h;
   }
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height); 
    }
};
class Triangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height)/2; 
    }
};

int main(void)
{
     Rectangle Rect;
     Triangle  Tri;

     Rect.setWidth(5);
     Rect.setHeight(7);

     cout << "Rectangle area: " << Rect.getArea() << endl;

     Tri.setWidth(5);
     Tri.setHeight(7);

     cout << "Triangle area: " << Tri.getArea() << endl; 

     return 0;
}

والنتيجة: منطقة المستطيل: 35 منطقة المثلث: 17

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

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