Я добавлю Подкаст безопасности Криптограммы . В основном, Dan Henage, читающий Bruce Schneier новостная рассылка Криптограмма.
большинство других подкастов я слушаю, чтобы быть упомянутым (TwiT, безопасность Теперь!, Расшатанные Фанаты).
мой 2c
Хороший хороший вопрос. Мне действительно хотелось бы, чтобы в Стандарте было более ясно, каково предполагаемое использование. Может быть, должен быть документ C ++ Rationale, который находится рядом со стандартом языка. В любом случае, вот подход, который я использую:
(a) Я не знаю о существовании такого списка. Вместо этого я использую следующий список, чтобы определить, может ли тип стандартной библиотеки быть разработан для наследования от:
виртуальных
методов, то вам не следует использовать это как база. Это исключает std :: vector
и т. Д. виртуальные
методы, то он является кандидатом для использования в качестве базового класса. друг
утверждений, плавающих вокруг, затем держитесь подальше, поскольку, вероятно, существует проблема инкапсуляции. std :: char_traits
) является довольно хорошей подсказкой, что вы не должны использовать его в качестве основы. К сожалению, я не знаю хорошего всеобъемлющего или черно-белого список. Я обычно исхожу из интуиции.
(b) Я бы применил здесь LSP . Если кто-то вызывает what ()
для вашего исключения, то его наблюдаемое поведение должно соответствовать поведению std :: exception
. Я не думаю, что это действительно вопрос соответствия стандартам, а скорее вопрос правильности. Стандарт не t требуют, чтобы подклассы заменяли базовые классы. На самом деле это просто "лучший метод" .
Стандартная библиотека C ++ не является единым целым. Это результат объединения и принятия нескольких различных библиотек (большая часть стандартной библиотеки C, библиотека iostreams и STL являются тремя основными строительными блоками, и каждый из них был определен независимо)
. библиотека, как вы знаете, обычно не предназначена для создания на ее основе. Он использует универсальное программирование и, как правило, избегает ООП.
Библиотека IOStreams является гораздо более традиционной ООП и в значительной степени внутренне использует наследование и динамический полиморфизм - и ожидается, что пользователи будут использовать те же механизмы для ее расширения. Пользовательские потоки обычно пишутся производными от самого класса потока, или класс streambuf
, который он использует для внутренних целей. У обоих из них есть виртуальные методы, которые можно переопределить в производных классах.
std :: exception
- другой пример.
И, как сказал Д.Шоули, я бы применил LSP к ваш второй вопрос. Всегда должна быть законная замена базового класса производным. Если я вызываю exception :: what ()
, он должен следовать контракту, указанному в классе exception
, независимо от того, откуда взялся объект exception
, или на самом деле это производный класс, преобразованный вверх. И в этом случае этот контракт является стандартным обещанием вернуть NTBS. Если вы заставили производный класс вести себя иначе, вы нарушите стандарт, потому что объект типа std ::
Чтобы ответить на вопрос 2):
Я считаю, что да, они будут связаны описанием интерфейса стандарта ISO. Например, стандарт позволяет переопределить оператор new
и оператор delete
глобально. Однако стандарт гарантирует, что operator delete
не является операцией с нулевыми указателями.
Несоблюдение этого, безусловно, неопределенное поведение (по крайней мере, для Скотта Майерса). Я думаю, мы можем сказать, что то же самое верно по аналогии для других областей стандартной библиотеки.
Некоторые вещи в функциональном
, например больше <>
, меньше <>
и mem_fun_t
являются производными от unary_operator <>
и binary_operator <>
. Но, IIRC, это дает вам только некоторые определения типов.
Экономное правило: «Любой класс может быть использован в качестве базового класса; ответственность за его безопасное использование в отсутствие виртуальных методов, включая виртуальный деструктор, полностью принадлежит производному автору. " Добавление члена, не являющегося POD, в дочерний элемент std :: exception - такая же ошибка пользователя, как и в производном классе std :: vector. Идея о том, что контейнеры не «предназначены» для использования в качестве базовых классов, является инженерным примером того, что профессора литературы называют ошибкой авторского намерения.
Преобладает принцип IS-A. Не производите D из B, если D не может заменить B во всех отношениях в публичном интерфейсе B, включая операцию удаления указателя B. Если B имеет виртуальные методы, это ограничение менее обременительно; но если у B есть только невиртуальные методы, все еще возможно и законно специализироваться на наследовании.
C ++ мультипарадигматичен. Библиотека шаблонов использует наследование, даже наследование от классов без виртуальных деструкторов, и таким образом демонстрирует на примере, что такие конструкции безопасны и полезны; предназначались ли они - вопрос психологический.
по вопросу 2), в соответствии со стандартом C ++, производный класс исключения должен указывать спецификацию no-throw, т.е. throw () , а также возвращать ненулевое значение . Во многих случаях это означает, что производный класс исключений не должен использовать std :: string, так как std :: string сам может вызывать в зависимости от реализации.
Думаю, что на второй вопрос ответ положительный. Стандарт говорит, что член what std :: exception должен возвращать значение, отличное от NULL. Не имеет значения, есть ли у меня значение стека, ссылки или указателя на std :: exception. Возврат what () ограничен стандартом.
Конечно, можно вернуть NULL. Но я считаю, что такой класс не соответствует стандартам.
Я знаю этот вопрос старый, но я хотел бы добавить сюда свой комментарий.
Уже несколько лет я использую класс CfgValue
, который наследуется от std :: string, хотя документация (или какая-то книга или какой-то стандартный документ, у меня сейчас нет исходного кода) говорит, что пользователи не должны наследовать от std :: string. И этот класс содержит комментарий «TODO: удалить наследование из std :: string» с годами.
Класс CfgValue просто добавляет некоторые конструкторы, сеттеры и геттеры для быстрого преобразования между строками и числовыми и логическими значениями, а также преобразование из кодировки utf8 (хранится в std :: string) в кодировку ucs2 (хранится в std :: wstring) и т. Д. .
Я знаю, есть много разных способов сделать это, но это просто чрезвычайно удобно для пользователя, и он отлично работает (что означает, что не ломается. любые концепции stdlib, обработка исключений и т. д.):
class CfgValue : public std::string {
public:
...
CfgValue( const int i ) : std::string() { SetInteger(i); }
...
void SetInteger( int i );
...
int GetInteger() const;
...
operator std::wstring() { return utf8_to_ucs16(*this); }
operator std::wstring() const { return utf8_to_ucs16(*this); }
...
};
Что касается вашей части B, от 17.3.1.2 «Требования», пункт 1:
Библиотека может быть расширена программой C ++. Каждое предложение, как применимо, описывает требования, которые такие расширения должны соответствовать. Такие расширения, как правило, являются одним из следующих:
- шаблон аргументов
- полученные классы
- контейнеры, итераторы и / или алгоритмы, соответствующие конвенции по интерфейсу
, в то время как 17.3 является информативным вместо того, чтобы привязать, намерение Комитета Производное поведение класса ясно.
Для других очень похожих точек расширения есть четкие требования:
В последнее время не ясно для меня, что список в скобках является исчерпывающим, но учитывая, насколько конкретно каждый упомянутый случай рассматривается в следующем абзаце, это Будьте растягиваться, чтобы сказать, текущий текст был предназначен для покрытия полученных классов. Кроме того, что 17.4.3.6/1 текст не изменился в проекте 2008 года (где он в 17,6,4,8), и я не вижу проблем , касающихся виртуальных методов либо полученных классов.