2009-07-02 6 views
50

Общепринято считать, что стандартная библиотека C++ обычно не предназначена для расширения с использованием наследования. Разумеется, я (и другие) критиковал людей, которые предлагают вывести из таких классов, как std::vector. Тем не менее, этот вопрос: c++ exceptions, can what() be NULL? заставило меня понять, что существует хотя бы одна часть стандартной библиотеки, которая предназначена для расширения - std::exception.Расширение стандартной библиотеки C++ по наследству?

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

  1. Существует ли какие-либо другие классы стандартной библиотеки, которые предназначены извлекаемыми из?

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

ответ

36

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

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

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

К сожалению, я не знаю подробного описания или черный и белый. Обычно я чувствую себя в кишечнике.

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

+0

Вы упомянули, что не наследуете из классов, у которых нет виртуальных членов, - но тогда вы также упомянули классы политики.Таким образом (например), что не так с наследованием от распределителя (в частном порядке)? –

+3

Я бы добавил, что некоторые вещи предназначены для небольшого града ree расширения через наследование, такие как 'std :: stack' и' std :: queue', потому что они имеют ** protected **, поскольку единственная причина для защищаемых вещей - разрешить дочернему классу читать данные. Очевидно, вам нужно быть очень осторожным в том, что вы делаете с этим. –

17

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

4

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

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

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

3

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

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

4

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

5

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

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

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

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

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

1

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

4

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

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

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

1

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

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

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

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

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

В то время как 17,3 познавательно вместо того, чтобы связывать, намерение комитета в отношении поведения производных классов является ясным.

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

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

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

Смежные вопросы