О переходе с базового класса на указатель на подкласс - PullRequest
5 голосов
/ 30 сентября 2010

Инструмент статической проверки показывает нарушение в приведенном ниже коде:

class CSplitFrame : public CFrameWnd  
...
class CVsApp : public CWinApp
CWnd* CVsApp::GetSheetView(LPCSTR WindowText)
{
 CWnd* pWnd = reinterpret_cast<CSplitFrame*>(m_pMainWnd)->m_OutputBar.GetChildWnd(WindowText);
 return pWnd;
}

Сообщение об ошибке: Класс «CSplitFrame» наследуется от класса «CWnd»

Описание: Избегать приведения вниз иерархии наследования. Это правило обнаруживает приведение от указателя базового класса к указателю подкласса.

Преимущества: Разрешение приведения вниз по иерархии наследования приводит к проблемам с обслуживанием, а уклонение от базового класса всегда недопустимо.

Ссылки:

  1. Скотт Мейерс, "Эффективный C ++: 50 конкретных способов улучшить ваши программы и дизайн", второе издание, Addison-Wesley, (2005 г.) Pearson Education, Inc., глава: "Наследование и объектно-ориентированный дизайн", пункт 39
  2. БОРЬБА С СОВМЕСТНЫМИ УСТРОЙСТВАМИ, АВТОМОБИЛЬ, СТАНДАРТЫ КОДИРОВАНИЯ C ++ Глава 4.23 Преобразования типов, правило AV 178

Как вы думаете, это хорошая практика, чтобы не понижать указатель базового класса на указатель подкласса? Почему и когда я должен следовать этому правилу?

Ответы [ 4 ]

10 голосов
/ 30 сентября 2010

reinterpret_cast, безусловно, плохая идея, независимо от стандартов кодирования или теории ООП. Это должно быть dynamic_cast или boost :: polymorphic_downcast .

Что касается главы 39 Effective C ++, она концентрируется на проблемах обслуживания, вызванных необходимостью понижать значение до нескольких различных типов и проверкой возвращаемых значений dynamic_cast на возможные сбои, что приводит к множественным ветвям в коде:

Важно следующее: стиль программирования if-then-else, к которому неизбежно приводит даункастинг, значительно уступает использованию виртуальных функций, и вы должны зарезервировать его для ситуаций, в которых у вас действительно нет альтернативы.

4 голосов
/ 06 октября 2010

Давайте рассмотрим некоторые из приведенных ниже примеров в MFC:

CButton * из CWnd *

CWnd* wnd = GetDlgItem(IDC_BUTTON_ID);
CButton* btn = dynamic_cast<CButton*>(wnd);

CChildWnd * из CFrameWnd *

CChildWnd * pChild = ((CSplitFrame*)(AfxGetApp()->m_pMainWnd))->GetActive();

Тамдействительно некоторые из ограничений дизайна MFC.

Благодаря тому, что CWnd обеспечивает базовую функциональность всех оконных классов в MFC, он даже служит базовым классом View, Dialog, Button и т. Д.

Если мы хотим избежать снижения рейтинга, возможно,нам нужен MFC-хакер, чтобы разделить CWnd на меньшее количество частей?

Теперь перейдем к другому вопросу: как устранить нарушение, мое скромное мнение состоит в том, чтобы попытаться избежать небезопасного понижения рейтинга с помощью безопасного понижения рейтинга:

Parent *pParent = new Parent;
Parent *pChild = new Child;

Child *p1 = static_cast<Child*>(pParent);   // Unsafe downcasting:it assigns the address of a base-class object (Parent) to a derived class (Child) pointer
Parent *p2 = static_cast<Child*>(pChild);   // Safe downcasting:it assigns the address of a derived-class object to a base-class pointer

Это хорошая практика для использования безопасного даункинга, хотя нарушение все еще существует, мы просто подавим нарушение с помощью данного объяснения.

Немного полезной ссылки:
http://support.microsoft.com/kb/108587
http://blog.csdn.net/ecai/archive/2004/06/26/27458.aspx
http://www.codeproject.com/KB/mcpp/castingbasics.aspx
http://www.bogotobogo.com/cplusplus/upcasting_downcasting.html

Наконец, спасибо за различные полезные ответы от всех вас.
Они действительно очень полезны.

0 голосов
/ 04 октября 2010

Указатели класса Downcasting, как правило, следует избегать любой ценой в правильно разработанном ООП-коде. Тем не менее, иногда такие преобразования необходимы, особенно если вы используете какую-то другую библиотеку / код, который был разработан, чтобы требовать такие преобразования в клиентском коде. MFC является одним из примеров такой библиотеки.

Когда действительно необходимы откаты, они никогда не должны выполняться с reinterpret_cast. Правильный способ выполнить приведение - либо dynamic_cast, либо static_cast.

Довольно часто люди используют reinterpret_cast, когда видят приведение в стиле C, используемое для понижения в коде, и решают преобразовать его в приведение в стиле C ++, неправильно полагая, что приведение в стиле C в таком контексте эквивалентно reinterpret_cast. В действительности приведение в стиле C, применяемое к паре типа родитель-потомок в любом направлении, эквивалентно static_cast с некоторыми дополнительными привилегиями (приведение в стиле C может пробить защиту доступа). Итак, опять же, использование приведений в стиле C для этой цели неправильно, но правильная замена C ++ - это не reinterpret_cast, а dynamic_cast или static_cast.

0 голосов
/ 04 октября 2010

Мне не кажется, что на самом деле вам необходимо выполнять приведение или, по крайней мере, не так, как вы это делаете.Ваша функция должна возвращать CWnd, поэтому вам не нужно приводить к CSplitFrame.

Я бы ожидал, что GetChildWnd вернет CWnd* или подобное;почему вы не можете написать что-то вроде:

CWnd* CVsApp::GetSheetView(LPCSTR WindowText)
{
   return m_pMainWnd->m_OutputBar.GetChildWnd(WindowText);
}
...