Наследование от стандартных классов.
Тогда я не уточнил в чем преимущество такого подхода. Можно предположить, что если логика работы стандартного контейнера изменится очень сильно, или если этот контейнер признают устаревшим и исключат из STL, то в случае с наследованием мне придется менять весь код, в котором я использовал свой производный класс, а в случае с агрегированием, мне надо будет изменить только этот класс.
Но с другой стороны, все равно в своем коде я повсеместно использую контейнеры STL, и код все равно придется весь переделывать. Да и переопределить используемые операции в новом классе возможно, если я знаю, какие из них использовались.
Недавно, когда я говорил с hardcase, он сказал:
В общем-то, такой подход приведен в разной документации по работе с GUI. И даже без того, что производный класс будет абстрактным. Но ведь в данном случае, если применять модель MVC, то от реализации Form будет зависеть как Вид, так и Контроллер. И если логика Form поменяется, то придется перелопачивать 2/3 кода, вместо 1/3 (только того класса, который агрегирует объект класса Form).
Можно сказать, что это паранойя, что логика Form не поменяется. Но ведь мы можем вообще отказаться от Windows Forms, от .Net. Можем перенести весь код на другую, аналогичную платформу, типа Java.
Вопрос: какие еще есть аргументы за и против наследования от стандартных классов?
если, будем надеяться, удобство у программиста не выходит за рамки разумного :)
Например, std::vector не конролирует выход диапазон, если обращаться просто по индексу (хотя можно обращаться с помощью at и получим исключение). И у Страуструпа в книге (The C++ Programming Language, Third Edition, глава 3.7.2) приведен пример где он наследуется от vector и перегружает оператор [].
Недавно кто-то на форуме хотел получить строку с дополнительными полями, а до этого кому-то требовался хитрый list.
Если подходить со стороны .NET, то здесь имеется известный формальный подход. Любые системные классы не помеченные sealed могут быть наследованы пользователем - это, что называется, по-определению (большая часть классов BCL - герметичные). Кроме того, эти классы, как правило, обладают обратной совместимостью - компиляция старого кода (код для .net 1.1 под .net 2.0) обычно происходит, но не без ахтунгов - устаревшие классы и члены классов помечаются специальным атрибутом ObsoleteAttribute.
Задача переноса кода в на другой язык под .NET обычно нецелессобразна по понятным причинам. Задача переноса кода на другую "аналогичную" платформу с моей точки зрения вообще - паранойя.
Верно, ведь определяющей в разработке была скорость. Безопасная версия STL такое контроллирует. В обычной проверяешь сам. Я думаю так, если досконально понимаешь внутренние механизмы STL (как, полагаю, Страуструп) - можно спокойно наследоваться от ее классов, если нет - лучше ненадо :). Я, например, особо в STL не разбираюсь и при необходимости также сделаю агрегирование - уберегу себя от возможных "непонятных" ошибок. Да так в общем-то с любой библиотекой классов, например с VCL та же ерунда при написании компонент - можно унаследоваться и сделать как тебе показалось надо, потом это будет работать в 2х случаях из 3. С некоторыми моментами я для себя разобрался, но открываю очередной сторонний компонент и вижу ахтунг - автор лет 5 пишет компоненты и до сих пор использует кривые приемы.
Жаль пропустил мимо ушей темы со строками и листом...
Кстати вот спецификатор ограничения вседозволенности sealed (NET, аналог в Яве) и маркировка некоторых методов advanced overridable в MFC наверное для этого сущестуют - типа no servicable components inside - не умеешь не берись.
Полагаю, что просто не гарантируют, что в следующей версии библиотеки (платформы или чего там еще) этот класс не будет удален, изменен или переименован.
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.
то бишь непредусмотренное, непланированное наследование. Но положительных побочных эффектов достаточно много. В общем думаю вопрос этот без ответа - кто изначально и зачем это придумал.
Но с другой стороны, все равно в своем коде я повсеместно использую контейнеры STL, и код все равно придется весь переделывать. Да и переопределить используемые операции в новом классе возможно, если я знаю, какие из них использовались.
Есть количественные метрики, определяющие, насколько одна часть кода зависит от другой. Например, в ООП, это нестабильность пакета, выраженная формулой I / (I + E), где I - количество типов, зависящих от типов вовне пакета, а E - количество внешних типов, зависящих от типов внутри пакета. Это означает, что некоторая часть кода тем более нестабильна, чем больше она зависит от остальнных частей кода, а, значит, и вероятность её изменения под воздействием изменения в других участках программы линейно зависит от нестабильности. Очевидно, что если рассматривать платформу для разработки приложений (пусть это будет STL), то количество потенциально зависящих от неё классов E должно стремиться к бесконечности, а, стало быть, нестабильность платформы должна (в идеале) предельно приближаться к нулю. Использование классов платформы при расчёте метрики может быть любым: хоть наследование, хоть делегирование. Иными словами, используя стандартный код, мы в любом случае целиком и полностью зависим от него, наследуемся ли мы от отдельно содержащихся в нём классов или нет - без разницы. Тут уж проектировщик сам решает, что более нестабильно: его решение или платформа. Например, при кроссплатформенном решении, более нестабильной окажется платформа, и зависимости от неё придётся разрежать путём введения дополнительных абстракций и добавления кода, нередко реализующего сквозную функциональность. С другой стороны, нестабильность не отражает гибкость. Например, способы повышения гибкости при наследовании ограничены лишь полиморфизмом. Делегирование - более гибкий инструмент (где-то было формально доказано, что наследование всегда может быть заменено делегированием + реализацией интерфейса), но его применение влечёт за собой увеличение объёма кода, а иногда и усложняет понимание логики работы системы.
Там же, где подразумевается, что платформа - наиболее стабильный пакет в решении, можно пользоваться следующей политикой: если нас устраивает гибкость стандартного компонента - мы наследуемся от него; если же набора перегружаемых методов нам не хватает, то мы применяем делегирование. Не забываем и о принципе подстановки: если мы передаём ссылку на объект производного типа в метод, принимающий ссылку на объект базового типа, то ход выполнения метода и состояние объекта должны оставаться корректными, в противном случае отношение is-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;
}
Да. Ступил я. Для таких целей я всегда использую абстрактные классы. Поэтому забыл, что так можно использовать и конкретный класс.
В данном случае, я привык писать только:
B *p = new B();
или просто
B b;
Тогда никаких утечек быть не должно.
Все тут так. К примеру, мы не можем повлиять на не-виртуальные наследуемые члены (изменить их семантику). А также мы не должны внедряемым функционалом противоречить концепции работы с классом, заложенной во всех его предках.
думаю, там лишнее слово "лишь". Оно переворачивает весь смысл. Получается, будто я могу менять все что угодно, и только где полиморфизм - там я не могу ничего изменить.
Вроде того.
{
template<class Type> void VecExt1(std::vector<Type>& vec)
{
...
}
}
А если данные какие-нибудь надо добавить, а не функции? Или добавить закрытые данные и функции, которые используют эти данные и открытые функции базового класса?
Например, мне надо, чтобы дочерний класс vector при выполнении push_back выполнял какую-то работу с дополнительными закрытыми данными, а затем производил функцию push_back родительского класса.
Kogrom, если ты уверен, что порожденный от STL класс не будет затронут в части его полиморфности, т.е. не будет уничтожен или передан по ссылке на его родителя
где может иметь место срезка,
то наследуйся, если это, действительно, оправдано.