C ++ Design: конструкторы базового класса и отношение is-a или has-a для пользовательского строкового класса

Я изучаю C ++ и хочу реализовать собственный класс строки, MyTextProc::Word, чтобы добавить некоторые функции std::stringнапример, обращение строки, преобразование регистра, перевод и т. д.

Кажется, что это лучше всего сделать, используя отношения is-a:

namespace MyTextProc {
class Word : public string {
/* my custom methods.... */
};
}

Я не указываю никаких конструкторов для моего класса, но приведенное выше определение Word класс предоставляет только конструкторы по умолчанию void и copy — cant Word просто наследовать все открытые строковые конструкторы?

Было бы хорошо иметь Word работать так же, как string, Я не добавляю никаких свойств к строке; я должен действительно реализовать каждый отдельный конструктор строки, базовый класс, чтобы реализовать мой новый подкласс?

Это лучше всего сделать, используя отношения has-a? Должен ли я просто реализовать const string& конструктор и требовать от клиентов передать строковый объект для строительства? Должен ли я переопределить все строковые конструкторы?

3

Решение

Добро пожаловать в ад С ++.

Вы только что коснулись одного из самых противоречивых аспектов C ++: std :: string не является полиморфным и конструкторы не наследуются.

Единственный «чистый» способ (который не вызовет никакой критики) — это встраивание std :: string в качестве члена, делегирующего ВСЕ ЕГО МЕТОДЫ. Хорошая работа!

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

  • В std :: string нет виртуальных методов, поэтому, если вы ее получите, вы не получите полиморфный тип.
  • Это означает, что если вы передадите Word функции хранения данных, и эта функция вызовет строковый метод, ваш метод переопределения не будет вызываться и
  • какое бы ни было распределение Word через new нельзя указывать на строку *: удаление через такой указатель приведет к неопределенному поведению
  • Все унаследованные методы, которые принимают string и возвращают string-s, будут работать, но они будут возвращать строку, а не Word.

Что касается конструкторов, они НЕ Унаследованы. Наследование конструкции по умолчанию является иллюзией, поскольку синтезированная по умолчанию реализация по умолчанию, копирование и присвоение компилятором неявно вызывает базу.

В C ++ 11 обходной путь может быть

class Word: public std::string
{
public:
template<class... Args>
Word(Args&&... args) :std::string(std::forward<Args>(args)...)
{}

//whatever else
};

Это дает аргументы любого типа, которые должны быть приведены при вызове подходящего std :: sting ctor (если он существует, в противном случае происходит ошибка компиляции).

Теперь решите сами, каким должен быть дизайн. Может быть, вы придете с обычным std :: string и независимым набором свободных функций.

Другим (несовершенным) способом может быть сделать Word не наследующим, а встраивающим std :: string, сконструированным, как указано выше, и неявным образом преобразуемым в std :: string. (плюс наличие явного метода str ()). Это позволяет вам использовать слово в качестве строки, создавать слово из строки, но не использовать слово «вместо» строки.

Еще одна вещь, которую нужно отучить (может быть из Java …): не привязывайте себя к правилу ООП «is-a = наследование и has-a = embedding». Все объекты стандартной библиотеки C ++ не являются объектами в смысле ООП, поэтому все методологии ООП в этом контексте имеют ошибки.

Вы должны решить в своем случае, каков компромисс между простым кодированием (и хорошим применением парадигмы «не повторяйся», намного проще с наследованием) или простым обслуживанием (а встраивание делает ваш код менее подверженным использованию ошибочно от других)


Это ответ на комментарий ниже:

«отсутствие полиморфизма стандартных классов C ++. Почему это так? Новичку, как я, кажется, что не реализация библиотек std C ++ с использованием виртуальных функций побеждает тот язык, который они предназначены для обогащения !!!«

Ну … да и нет!

Поскольку вы цитируете PERL, учтите, что
— PERL — это язык сценариев, где типы являются динамическими.
— Java — это язык, где типы статичны, а объекты динамичны
— C ++ — это язык, в котором типы статичны, а объект статичен (а динамическое размещение объекта является явным)

Теперь в Java объекты всегда распределяются динамически, а локальные переменные являются «ссылкой» на эти объекты.
В C ++ локальные переменные сами являются объектами и имеют семантику значений. И стандартная библиотека C ++ разработана не как набор основ для расширения, а как набор типов значений, для которых генерируется код с помощью шаблонов.

Подумай std::string как-то работает так же, как int работает: вы ожидаете получить от int, чтобы получить «больше методов» или изменить поведение некоторых из них?

Спорный аспект здесь заключается в том, что — чтобы быть согласованным с этим design- std :: string должен просто управлять своей внутренней памятью, но не должен иметь методов. Istead, строковые функции shold были реализованы как шаблоны, так что их можно использовать как «алгоритм» с любым другим классом, демонстрирующим такое же внешнее поведение std :: string. Что-то, что дизайнеры не сделали.

Они поместили много методов в него, но они не делают его полиморфным, чтобы по-прежнему сохранять семантику значений, создавая таким образом неоднозначный дизайн и сохраняя для наследования единственный способ «повторного использования» этих методов без их повторного объявления. Это возможно, но с ограничением, которое я вам сказал.

Если вы хотите эффективно создать новую функцию, чтобы иметь «полиморфизм по значению», используйте teplates: intead of

std::string capitalize(const std::string& s) { .... }

сделать что-то вроде

template<class String>
String capitalize(const String& s) { .... }

Чтобы ваш код мог работать с любым классом, имеющим одинаковый строковый интерфейс относительно символов, для любого типа символов.

3

Другие решения

Честно говоря, я бы реализовал нужные вам методы как функции, которые принимают строку и возвращают строку. Их будет проще тестировать, отсоединять и легко использовать. Когда в C ++, не всегда тянуться к классу, когда функция будет делать. Фактически, когда вы входите в шаблоны, вы можете создать шаблонную функцию без определения и специализации для базового строкового класса. Таким образом, вы всегда будете знать, имеет ли тип строки, к которому вы прикасаетесь, собственный метод (и да, если вы будете взаимодействовать с Microsoft, вы обнаружите, что существует 50 миллионов реализаций строк).

2