Проблема наследования алмазов с использованием сторонней библиотеки - PullRequest
2 голосов
/ 07 марта 2011

Я, кажется, нашел случай, когда я должен страдать от "страшной" проблемы наследования алмазов. Тем не менее, код, кажется, работает просто отлично. То, что я не могу понять наверняка, - то, если может быть проблемой.

Вот настройки. Я использую MFC и расширил CEdit, чтобы добавить пользовательскую обработку сообщения о щелчке мышью. Затем я наследую от этого класса и класса, написанного сторонним разработчиком (в этом примере назовите его Бобом). После этого я могу вернуть либо свой особый элемент управления, либо расширенную версию элемента управления Боба. Проблема в том, что библиотека Боба не может быть изменена, и оба наших кода в конечном итоге наследуются от CEdit (и CWnd в этом отношении).

Пример кода:

class A : public CEdit {...}       // From Bob's library
class B : public A {...}           // From Bob's library
class BobsEdit : public B {...}    // From Bob's library

// My version which handles WM_LBUTTONDOWN, WM_CREATE 
// and does a couple other cool things.
class MyEdit : public CEdit 
{
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct) 
    {
         if ( !CEdit::Create(...) ) return -1;

         ...set some style stuff... 
    }

    afx_msg void OnLButtonDown(UINT nFlags,CPoint point) {}  // Override CWnd handler
}  

class MyBobsEdit : public BobsEdit, public MyEdit {}   // My version of Bob's control


// CBobsUser returns just a standard CEdit and BobsEdit control
// This is also from Bob's library.

class CBobsUser   
{
    CWnd* GetMeAnEditBox() 
    {
        CEdit* pEdit;
        if ( ...some condition... )
          pEdit = new CEdit();
        else
          pEdit = new BobsEdit();

        ...
        return pEdit;
    }
}

// CMyUser overrides Bob's GetMeAnEditBox and returns 
// one of my custom controls (with the new cool handler).
class CMyUser : public CBobsUser
{    
...

    CWnd* GetMeAnEditBox() 
    {
        MyEdit* pEdit;
        if ( ...some condition... )
          pEdit = new MyEdit();
        else
          pEdit = new MyBobsEdit();

        ...
        return pEdit;
    }                 
}

Итак ... Вопросы:

  1. Почему это, похоже, не страдает от проблемы наследования алмазов?
  2. Есть ли проблема, которую я не вижу с этим дизайном, которая может укусить меня в будущем?
  3. Есть ли другой способ исправить это, если я не могу изменить код на одной стороне ромба (т.е. я не могу объявить CEdit виртуальным на обеих сторонах?)

Спасибо!

1 Ответ

3 голосов
/ 07 марта 2011

Объявление 1: Поскольку никто никогда не узнает, что объект CBobsEdit. Вы создаете объект как MyBobsEdit, но сразу же приводите его к MyEdit, поэтому все вызовы методов выполняются на MyEdit, и не возникает никаких ошибок при вызове, а само приведение также не является неоднозначным. Функциональность CBobsEdit никогда не используется (у вас нет методов в подклассе). Он создан, но никогда не добавляется к родителю, поэтому он никогда не отображается и никогда не используется.

Объявление 2: Ну, вы вообще не используете BobsEdit. Что, я полагаю, не то, что вы хотели.

Объявление 3: Вы можете сделать MyEdit шаблоном, который унаследован от его аргумента шаблона, и наследовать его непосредственно от CEdit в одном случае и от CBobsEdit в другом случае. Эту технику часто называют «миксин». Как:

template <typename BaseEditT>
class MyEdit : public BaseEditT { ... }

К сожалению MyEdit<CEdit> и MyEdit<CBobsEdit> не связаны между собой. Если вы можете сохранить указатель как CEdit (который всегда является базовым классом), вам придется определить интерфейс, реализовать этот интерфейс в MyEdit и сохранить указатель на этот интерфейс. Интерфейс должен содержать оператор приведения к CEdit&CEdit const&), и тогда вы сможете вызывать любые методы CEdit для него. Как это:

class IMyEdit {
    virtual operator CEdit &() = 0;
    virtual operator CEdit const &() const = 0;
};

template <typename BaseEditT>
class MyEdit : public BaseEditT {
    operator CEdit &() { return *this; }
    operator CEdit const &() const { return *this; }
};

Обратите внимание, что не только код, создающий объекты, должен видеть определение шаблона MyEdit, поэтому вы можете поместить его в отдельный файл и включать его только в том случае, если вы определите конструктор CMyUser, чтобы избежать наказания за время компиляции.

...