Подтвердить что ты не робот

Расширение стандартной библиотеки С++ по наследству?

Общеизвестно, что стандартная библиотека С++ обычно не предназначена для расширения с использованием наследования. Конечно, я (и другие) критиковал людей, которые предполагают получение таких классов, как std::vector. Однако этот вопрос: исключения С++, может ли() быть NULL? заставило меня понять, что есть хотя бы одна часть стандартной библиотеки, которая должна быть настолько расширенной - std::exception.

Итак, мой вопрос состоит из двух частей:

  • Существуют ли какие-либо другие классы стандартной библиотеки, которые должны быть получены из?

  • Если кто-либо из класса стандартной библиотеки, такого как std::exception, связан с интерфейсом, описанным в стандарте ISO? Например, будет ли стандартная конфигурация программой, использующей класс исключений, кто не имеет функции-члена what(), не возвращает NTBS (скажем, вернул нулевой указатель)?

4b9b3361

Ответ 1

Хороший хороший вопрос. Я действительно хочу, чтобы стандарт был немного более конкретным в отношении предполагаемого использования. Может быть, должен быть документ C Обоснование, который находится рядом с языковым стандартом. В любом случае, вот такой подход, который я использую:

(a) Я не знаю о существовании такого списка. Вместо этого я использую следующий список, чтобы определить, может ли тип стандартной библиотеки быть унаследован от:

  • Если у него нет методов virtual, вы не должны использовать его в качестве базы. Это исключает std::vector и т.п.
  • Если у него есть методы virtual, то он является кандидатом на использование в качестве базового класса.
  • Если существует множество операторов friend, плавающих вокруг, то избегайте их устранения, поскольку существует проблема с инкапсуляцией.
  • Если это шаблон, то посмотрите ближе, прежде чем наследовать его, поскольку вы можете настроить его с помощью специализаций.
  • Наличие механизма на основе политик (например, std::char_traits) - довольно хорошая подсказка, что вы не должны использовать его в качестве базы.

К сожалению, я не знаю хорошего всеобъемлющего или черно-белого списка. Обычно я чувствую себя как кишка.

(b) Я бы применил LSP здесь. Если кто-то звонит what() в ваше исключение, то наблюдаемое поведение должно соответствовать значению std::exception. Я не думаю, что это действительно вопрос соответствия стандартам, а также проблема правильности. Стандарт не требует, чтобы подклассы были подменяемы для базовых классов. Это действительно просто "лучшая практика".

Ответ 2

a) библиотека потока создается для наследования:)

Ответ 3

Что касается вашей части b, из 17.3.1.2 "Требования", пункт 1:

Библиотека может быть расширена с помощью программы на С++. Каждое предложение, если применимо, описывает требования, которые должны удовлетворять такие расширения. Такие расширения обычно являются одним из следующих:

  • Аргументы шаблона
  • Производные классы
  • Контейнеры, итераторы и/или алгоритмы, удовлетворяющие условиям интерфейса

В то время как 17.3 является информативным, а не обязательным, намерение комитета по производному классу очевидно.

Для других очень похожих точек расширения существуют четкие требования:

  • 17.1.15 "требуемое поведение" охватывает замену (оператор new и т.д.) и функции обработчика (завершающие обработчики и т.д.) и бросает все несовместимые действия в UB-land.
  • 17.4.3.6/1: "В некоторых случаях (функции замены, функции обработчика, операции с типами, используемыми для создания стандартных компонентов шаблона библиотеки), стандартная библиотека С++ зависит от компонентов, поставляемых программой на С++. Если эти компоненты не отвечают их требованиям, Стандарт не предъявляет никаких требований к реализации".

В последнем пункте мне не ясно, что список в скобках является исчерпывающим, но учитывая, как конкретно каждый упомянутый случай рассматривается в следующем параграфе, было бы сказано, что текущий текст предназначен для охвата производных классов, Кроме того, этот текст 17.4.3.6/1 не изменился в проекте 2008 года (где он в 17.6.4.8), и я не вижу никаких проблем, это виртуальные методы или производные классы.

Ответ 4

Стандартная библиотека С++ - это не единое целое. Это результат объединения и принятия нескольких разных библиотек (большая часть стандартной библиотеки C, библиотека iostreams и STL являются тремя основными строительными блоками, и каждая из них была указана независимо)

Часть STL библиотеки, как вы знаете, обычно не должна быть получена. Он использует общее программирование и обычно избегает ООП.

Библиотека IOStreams является гораздо более традиционным ООП и сильно использует наследование и динамический полиморфизм, и пользователи, как ожидается, будут использовать те же механизмы для его расширения. Пользовательские потоки обычно записываются путем получения либо самого класса потока, либо класса streambuf, который он использует внутренне. Оба они имеют виртуальные методы, которые могут быть переопределены в производных классах.

std::exception - еще один пример.

И, как сказал Д.Шаули, я применил бы LSP к вашему второму вопросу. Всегда должно быть правовым заменить базовый класс на производный. Если я вызываю exception::what(), он должен следовать контракту, указанному классом exception, независимо от того, откуда пришел объект exception, или действительно ли это производный класс, который был взвинчен. И в этом случае этот контракт является стандартным обещанием вернуть NTBS. Если вы сделали поведение производного класса по-другому, вы нарушите стандарт, потому что объект типа std::exception больше не возвращает NTBS.

Ответ 5

Чтобы ответить на вопрос 2):

Я считаю, что да, они будут связаны описанием интерфейса стандарта ISO. Например, стандарт позволяет переопределять operator new и operator delete глобально. Тем не менее, стандарт гарантирует, что operator delete является недействительным для нулевых указателей.

Не соблюдается это, безусловно, поведение undefined (по крайней мере, Скотт Майерс). Я думаю, мы можем сказать, что то же самое верно по аналогии для других областей стандартной библиотеки.

Ответ 6

Некоторые вещи из functional, такие как greater<>, less<> и mem_fun_t, получены из unary_operator<> и binary_operator<>. Но, IIRC, это только дает вам некоторые typedefs.

Ответ 7

Ловкое правило: "Любой класс может использоваться в качестве базового класса, а возможность его безопасного использования в отсутствие виртуальных методов, в том числе виртуального деструктора, - это полностью авторство". Добавление не-POD-члена в дочернем элементе std:: exception является той же ошибкой пользователя, что и в производном классе std::vector. Идея о том, что контейнеры не являются "предназначенными" для базовых классов, является инженерным примером того, что профессора литературы называют "Ошибочность авторского намерения".

Принцип IS-A доминирует. Не выводить D из B, если D не может заменить B во всех отношениях в открытом интерфейсе B, включая операцию удаления по указателю B. Если B имеет виртуальные методы, это ограничение менее обременительно; но если B имеет только невиртуальные методы, то все же возможно и законно специализироваться с наследованием.

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

Ответ 8

Во втором вопросе я считаю, что ответ - да. В стандарте указано, что член std:: exception должен возвращать значение, отличное от NULL. Не имеет значения, есть ли значение стека, ссылки или указателя для std:: exception. Возврат того, что() связано стандартом.

Конечно, можно вернуть NULL. Но я считаю, что такой класс не соответствует стандартам.

Ответ 9

w.r.t вопрос 2), согласно стандарту С++, производный класс исключений должен также указывать спецификацию no-throw i.e. throw() вместе с возвратом ненулевого значения. Это во многих случаях означает, что производный класс исключений не должен использовать std::string, поскольку сам std::string может бросать в зависимости от реализации.

Ответ 10

Я знаю, что этот вопрос старый, но я хотел бы добавить здесь свой комментарий.

С некоторых лет я использую class 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); }
    ...
};