Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Наследование от стандартных классов.

87
26 августа 2009 года
Kogrom
2.7K / / 02.02.2008
Однажды я на форуме привел код, в котором произвел наследование от стандартного контейнера STL. Уважаемый Green сказал, что это плохой тон и привел пример с агрегированием, в котором заключил контейнер в новый класс, а требуемые операции переадресовал этому контейнеру.

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

Но с другой стороны, все равно в своем коде я повсеместно использую контейнеры STL, и код все равно придется весь переделывать. Да и переопределить используемые операции в новом классе возможно, если я знаю, какие из них использовались.

Недавно, когда я говорил с hardcase, он сказал:

Цитата: hardcase
Наследовать нужно от того, что удобнее в каждой конкретной задаче. К примеру, класс Form далеко не абстрактный, но я создаю от него своего собственного абстрактного наследника и порождаю от него все формы в проекте.



В общем-то, такой подход приведен в разной документации по работе с GUI. И даже без того, что производный класс будет абстрактным. Но ведь в данном случае, если применять модель MVC, то от реализации Form будет зависеть как Вид, так и Контроллер. И если логика Form поменяется, то придется перелопачивать 2/3 кода, вместо 1/3 (только того класса, который агрегирует объект класса Form).

Можно сказать, что это паранойя, что логика Form не поменяется. Но ведь мы можем вообще отказаться от Windows Forms, от .Net. Можем перенести весь код на другую, аналогичную платформу, типа Java.

Вопрос: какие еще есть аргументы за и против наследования от стандартных классов?

14
26 августа 2009 года
Phodopus
3.3K / / 19.06.2008
Наверное первое, на что надо смотреть в такой ситуации - насколько данный конкретный класс предназначен для наследования. На мой взгляд контейнеры STL весьма и весьма законченная вещь (бесконечно "расширяема" из-за использования шаблонов), в то время как экземпляр Form как таковой не представляет из себя ничего дельного и от него просто "придется" наследоваться. Иносказав то что я говорю вполне может получиться что
Цитата:
Наследовать нужно от того, что удобнее


если, будем надеяться, удобство у программиста не выходит за рамки разумного :)

87
26 августа 2009 года
Kogrom
2.7K / / 02.02.2008
Цитата: Phodopus
На мой взгляд контейнеры STL весьма и весьма законченная вещь



Например, std::vector не конролирует выход диапазон, если обращаться просто по индексу (хотя можно обращаться с помощью at и получим исключение). И у Страуструпа в книге (The C++ Programming Language, Third Edition, глава 3.7.2) приведен пример где он наследуется от vector и перегружает оператор [].

Недавно кто-то на форуме хотел получить строку с дополнительными полями, а до этого кому-то требовался хитрый list.

5
26 августа 2009 года
hardcase
4.5K / / 09.08.2005
Цитата: Kogrom
Вопрос: какие еще есть аргументы за и против наследования от стандартных классов?

Если подходить со стороны .NET, то здесь имеется известный формальный подход. Любые системные классы не помеченные sealed могут быть наследованы пользователем - это, что называется, по-определению (большая часть классов BCL - герметичные). Кроме того, эти классы, как правило, обладают обратной совместимостью - компиляция старого кода (код для .net 1.1 под .net 2.0) обычно происходит, но не без ахтунгов - устаревшие классы и члены классов помечаются специальным атрибутом ObsoleteAttribute.

Задача переноса кода в на другой язык под .NET обычно нецелессобразна по понятным причинам. Задача переноса кода на другую "аналогичную" платформу с моей точки зрения вообще - паранойя.

14
26 августа 2009 года
Phodopus
3.3K / / 19.06.2008
Цитата: Kogrom
Например, std::vector не конролирует выход диапазон. И у Страуструпа в книге приведен пример где он наследуется от vector и перегружает оператор []. Недавно кто-то на форуме хотел получить строку с дополнительными полями, а до этого кому-то требовался хитрый list.


Верно, ведь определяющей в разработке была скорость. Безопасная версия STL такое контроллирует. В обычной проверяешь сам. Я думаю так, если досконально понимаешь внутренние механизмы STL (как, полагаю, Страуструп) - можно спокойно наследоваться от ее классов, если нет - лучше ненадо :). Я, например, особо в STL не разбираюсь и при необходимости также сделаю агрегирование - уберегу себя от возможных "непонятных" ошибок. Да так в общем-то с любой библиотекой классов, например с VCL та же ерунда при написании компонент - можно унаследоваться и сделать как тебе показалось надо, потом это будет работать в 2х случаях из 3. С некоторыми моментами я для себя разобрался, но открываю очередной сторонний компонент и вижу ахтунг - автор лет 5 пишет компоненты и до сих пор использует кривые приемы.
Жаль пропустил мимо ушей темы со строками и листом...
Кстати вот спецификатор ограничения вседозволенности sealed (NET, аналог в Яве) и маркировка некоторых методов advanced overridable в MFC наверное для этого сущестуют - типа no servicable components inside - не умеешь не берись.

87
26 августа 2009 года
Kogrom
2.7K / / 02.02.2008
Цитата: Phodopus
Кстати вот спецификатор ограничения вседозволенности sealed (NET, аналог в Яве) и маркировка некоторых методов advanced overridable в MFC наверное для этого сущестуют - типа no servicable components inside - не умеешь не берись.



Полагаю, что просто не гарантируют, что в следующей версии библиотеки (платформы или чего там еще) этот класс не будет удален, изменен или переименован.

14
26 августа 2009 года
Phodopus
3.3K / / 19.06.2008
Ну вообще майкрософт пишет
Цитата:

The sealed modifier is primarily used to prevent unintended derivation, but it also enables certain run-time optimizations. In particular, because a sealed class is known to never have any derived classes, it is possible to transform virtual function member invocations on sealed class instances into non-virtual invocations.


то бишь непредусмотренное, непланированное наследование. Но положительных побочных эффектов достаточно много. В общем думаю вопрос этот без ответа - кто изначально и зачем это придумал.

341
28 августа 2009 года
Der Meister
874 / / 21.12.2007
Цитата: Kogrom
Можно предположить, что если логика работы стандартного контейнера изменится очень сильно, или если этот контейнер признают устаревшим и исключат из STL, то в случае с наследованием мне придется менять весь код, в котором я использовал свой производный класс, а в случае с агрегированием, мне надо будет изменить только этот класс.

Но с другой стороны, все равно в своем коде я повсеместно использую контейнеры STL, и код все равно придется весь переделывать. Да и переопределить используемые операции в новом классе возможно, если я знаю, какие из них использовались.

Есть количественные метрики, определяющие, насколько одна часть кода зависит от другой. Например, в ООП, это нестабильность пакета, выраженная формулой I / (I + E), где I - количество типов, зависящих от типов вовне пакета, а E - количество внешних типов, зависящих от типов внутри пакета. Это означает, что некоторая часть кода тем более нестабильна, чем больше она зависит от остальнных частей кода, а, значит, и вероятность её изменения под воздействием изменения в других участках программы линейно зависит от нестабильности. Очевидно, что если рассматривать платформу для разработки приложений (пусть это будет STL), то количество потенциально зависящих от неё классов E должно стремиться к бесконечности, а, стало быть, нестабильность платформы должна (в идеале) предельно приближаться к нулю. Использование классов платформы при расчёте метрики может быть любым: хоть наследование, хоть делегирование. Иными словами, используя стандартный код, мы в любом случае целиком и полностью зависим от него, наследуемся ли мы от отдельно содержащихся в нём классов или нет - без разницы. Тут уж проектировщик сам решает, что более нестабильно: его решение или платформа. Например, при кроссплатформенном решении, более нестабильной окажется платформа, и зависимости от неё придётся разрежать путём введения дополнительных абстракций и добавления кода, нередко реализующего сквозную функциональность. С другой стороны, нестабильность не отражает гибкость. Например, способы повышения гибкости при наследовании ограничены лишь полиморфизмом. Делегирование - более гибкий инструмент (где-то было формально доказано, что наследование всегда может быть заменено делегированием + реализацией интерфейса), но его применение влечёт за собой увеличение объёма кода, а иногда и усложняет понимание логики работы системы.
Там же, где подразумевается, что платформа - наиболее стабильный пакет в решении, можно пользоваться следующей политикой: если нас устраивает гибкость стандартного компонента - мы наследуемся от него; если же набора перегружаемых методов нам не хватает, то мы применяем делегирование. Не забываем и о принципе подстановки: если мы передаём ссылку на объект производного типа в метод, принимающий ссылку на объект базового типа, то ход выполнения метода и состояние объекта должны оставаться корректными, в противном случае отношение is-a грубо нарушено и применено некорректно.

1.8K
28 августа 2009 года
_const_
229 / / 26.11.2003
Классы STL не имеют виртуальных деструкторов, поэтому наследование от них может привести к утечкам памяти.
87
28 августа 2009 года
Kogrom
2.7K / / 02.02.2008
Der Meister, интересно, хотя немного путано. Например:
Цитата: Der Meister
Например, способы повышения гибкости при наследовании ограничены лишь полиморфизмом.


что-то тут не так.

Цитата: _const_
Классы STL не имеют виртуальных деструкторов, поэтому наследование от них может привести к утечкам памяти.



Так там же конкретные классы. Может я слаб в теории, но насколько я помню, виртуальный деструктор им не требуется.

1.8K
28 августа 2009 года
_const_
229 / / 26.11.2003
Что значит "конкретные классы"? Вот пример:

Код:
class A
{
public:
    A() {};
    ~A() {};
};

class B : public A
{
private:
    int *p;
public:
    B() : A() { p = new int; };
    ~B() { delete p; };
};

int main(...)
{
    A *p = new B;
    delete p; // ку-ку
    return 0;
}
303
28 августа 2009 года
makbeth
1.0K / / 25.11.2004
Мне кажется, что агрегация нецелесообразна в том случае, если мы лишь немного изменяем поведение агрегируемого объекта. Т.е. какой смысл агрегировать vector, если наш класс практически повторяет его функциональность? Писать кучу методов-заплаток, которые практически все ничего не делают, кроме как вызывают аналогичные методы vector. Здесь, я думаю, агрегация неоправданна.
87
28 августа 2009 года
Kogrom
2.7K / / 02.02.2008
Цитата: _const_
Что значит "конкретные классы"?



Да. Ступил я. Для таких целей я всегда использую абстрактные классы. Поэтому забыл, что так можно использовать и конкретный класс.

В данном случае, я привык писать только:

B *p = new B();

или просто

B b;

Тогда никаких утечек быть не должно.

5
28 августа 2009 года
hardcase
4.5K / / 09.08.2005
Цитата: Kogrom
что-то тут не так.

Все тут так. К примеру, мы не можем повлиять на не-виртуальные наследуемые члены (изменить их семантику). А также мы не должны внедряемым функционалом противоречить концепции работы с классом, заложенной во всех его предках.

87
28 августа 2009 года
Kogrom
2.7K / / 02.02.2008
Цитата: hardcase
Все тут так.


думаю, там лишнее слово "лишь". Оно переворачивает весь смысл. Получается, будто я могу менять все что угодно, и только где полиморфизм - там я не могу ничего изменить.

5
28 августа 2009 года
hardcase
4.5K / / 09.08.2005
Цитата: Kogrom
думаю, там лишнее слово "лишь"

Вроде того.

1.8K
28 августа 2009 года
_const_
229 / / 26.11.2003
Вообще, никто не гарантирует, что реализация класса STL не будет меняться от версии к версии, следовательно, можно безопасно использовать только открытые методы. В результате смысл наследования теряется. При необходимости что-то добавить в данном случае лучше воспользоваться простыми ф-циями. Например, так.
 
Код:
namespace MySuperPuperSTLExtention
{
    template<class Type> void VecExt1(std::vector<Type>& vec)
    {
    ...
    }
}
87
28 августа 2009 года
Kogrom
2.7K / / 02.02.2008
Цитата: _const_
При необходимости что-то добавить в данном случае лучше воспользоваться простыми ф-циями.



А если данные какие-нибудь надо добавить, а не функции? Или добавить закрытые данные и функции, которые используют эти данные и открытые функции базового класса?

Например, мне надо, чтобы дочерний класс vector при выполнении push_back выполнял какую-то работу с дополнительными закрытыми данными, а затем производил функцию push_back родительского класса.

1.8K
03 сентября 2009 года
_const_
229 / / 26.11.2003
Тогда последовать совету от Green. Тем более, что в случае изменений в классе STL (что маловероятно) придется не переписывать ВЕСЬ код, а только подогнать класс-обертку под новые условия.
3
21 сентября 2009 года
Green
4.8K / / 20.01.2000
Раз уж я невольно стал виновником этого торжества, то нехорошо будет не вставить свои 5 коп.

Kogrom, если ты уверен, что порожденный от STL класс не будет затронут в части его полиморфности, т.е. не будет уничтожен или передан по ссылке на его родителя
 
Код:
void func(std::string& str);

где может иметь место срезка,
то наследуйся, если это, действительно, оправдано.
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог