Я изучаю 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&
конструктор и требовать от клиентов передать строковый объект для строительства? Должен ли я переопределить все строковые конструкторы?
Добро пожаловать в ад С ++.
Вы только что коснулись одного из самых противоречивых аспектов C ++: std :: string не является полиморфным и конструкторы не наследуются.
Единственный «чистый» способ (который не вызовет никакой критики) — это встраивание std :: string в качестве члена, делегирующего ВСЕ ЕГО МЕТОДЫ. Хорошая работа!
Другие способы могут быть найдены, но вы всегда должны позаботиться о некоторых ограничениях.
new
нельзя указывать на строку *: удаление через такой указатель приведет к неопределенному поведениюЧто касается конструкторов, они НЕ Унаследованы. Наследование конструкции по умолчанию является иллюзией, поскольку синтезированная по умолчанию реализация по умолчанию, копирование и присвоение компилятором неявно вызывает базу.
В 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) { .... }
Чтобы ваш код мог работать с любым классом, имеющим одинаковый строковый интерфейс относительно символов, для любого типа символов.
Честно говоря, я бы реализовал нужные вам методы как функции, которые принимают строку и возвращают строку. Их будет проще тестировать, отсоединять и легко использовать. Когда в C ++, не всегда тянуться к классу, когда функция будет делать. Фактически, когда вы входите в шаблоны, вы можете создать шаблонную функцию без определения и специализации для базового строкового класса. Таким образом, вы всегда будете знать, имеет ли тип строки, к которому вы прикасаетесь, собственный метод (и да, если вы будете взаимодействовать с Microsoft, вы обнаружите, что существует 50 миллионов реализаций строк).