Мне нужно мнение гуру C ++ по расширению std :: string - PullRequest
7 голосов
/ 10 января 2010

Я всегда хотел немного больше функциональности в строке STL. Поскольку подклассы типов STL - нет, нет, в основном я видел рекомендуемый метод расширения этих классов - просто писать функции (не функции-члены), которые принимают тип в качестве первого аргумента.

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

Некоторое время назад я придумал следующее:

class StringBox
{
public:
   StringBox( std::string& storage ) :
       _storage( storage )
   {
   }

   // Methods I wish std::string had...
   void Format(); 
   void Split();
   double ToDouble(); 
   void Join(); // etc...

private:
  StringBox();

  std::string& _storage;
};

Обратите внимание, что StringBox требует ссылки на std :: string для построения ... Это накладывает некоторые интересные ограничения на его использование (и, я надеюсь, означает, что это не способствует проблеме распространения класса строк) ... В мой собственный код, я почти всегда просто объявляю его в стеке в методе, просто чтобы изменить std :: string.

Пример использования может выглядеть следующим образом:

string OperateOnString( float num, string a, string b )
{
    string nameS;
    StringBox name( nameS );

    name.Format( "%f-%s-%s", num, a.c_str(), b.c_str() );

    return nameS;
}

Мой вопрос: что гуру C ++ сообщества StackOverflow думают об этом методе расширения STL?

Ответы [ 7 ]

19 голосов
/ 10 января 2010

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

И я хочу использовать $!---& при вызове методов! Смирись с этим. Если вы собираетесь писать код на C ++, придерживайтесь соглашений C ++. И очень важное соглашение C ++ - отдавать предпочтение функциям, не являющимся членами, когда это возможно.

Есть причина, по которой гуру C ++ рекомендуют это:

Улучшает инкапсуляцию, расширяемость и повторное использование. (std::sort может работать со всеми парами итераторов , потому что не является членом какого-либо одного итератора или класса контейнера. И как бы вы ни расширяли std::string, вы не можете нарушайте его, пока вы придерживаетесь функций, не являющихся членами. И даже если у вас нет доступа к исходному коду класса или вам не разрешено изменять его, вы все равно можете расширить его, определив функции, не являющиеся членами)

Лично я не вижу смысла в вашем коде. Разве это не намного проще, удобочитаемее и короче?

string OperateOnString( float num, string a, string b )
{
    string nameS;
    Format(nameS, "%f-%s-%s", num, a.c_str(), b.c_str() );
    return nameS;
}

// or even better, if `Format` is made to return the string it creates, instead of taking it as a parameter
string OperateOnString( float num, string a, string b )
{
    return Format("%f-%s-%s", num, a.c_str(), b.c_str() );
}

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

Что касается вашей проблемы, заключающейся в том, что трудно найти функции, не являющиеся членами, которые расширяют string, поместите их в пространство имен, если это вызывает озабоченность. Вот для чего они. Создайте namespace StringUtil или что-то еще и поместите их туда.

15 голосов
/ 10 января 2010

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

2 голосов
/ 10 января 2010

Я добавлю кое-что, что еще не было опубликовано. В библиотеке Boost String Algorithms используется подход с использованием бесплатных шаблонных функций, а предоставляемые ими строковые алгоритмы потрясающе многократно используются для всего, что выглядит как строка: std :: string, char *, std :: vector итераторные пары ... вы называете это! И они аккуратно помещают их в пространство имен boost :: алгоритма (я часто использую using namespace algo = boost::algorithm, чтобы сделать код манипуляции со строками более лаконичным).

Так что рассмотрите возможность использования бесплатных шаблонных функций для ваших строковых расширений и посмотрите на алгоритмы Boost String, чтобы сделать их «универсальными».

Для безопасного форматирования в стиле printf, проверьте Boost.Format . Может выводить в строки и потоки.

Я тоже хотел, чтобы все было функцией-членом, но сейчас я начинаю видеть свет. UML и doxygen всегда заставляют меня помещать функции в классы, потому что мне промыли мозги идея, что C ++ API == иерархия классов.

1 голос
/ 17 ноября 2012

Проблема со свободными функциями заключается в том, что они являются свободными функциями.

Я бы поспорил, что большинство из вас создали функцию, которая уже была предоставлена ​​STL, потому что вы просто не знали, что функция STL существует, или что она может делать то, что вы пытались выполнить.

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

Google: C ++ в строку

Сколько упомянуто результатов: std :: to_string

Я с такой же вероятностью найду какой-нибудь древний C-метод или самодельную версию, как и STL-версию любой данной функции.

Я очень предпочитаю методы-члены, потому что вам не нужно изо всех сил пытаться их найти, и вам не нужно беспокоиться о поиске старых устаревших версий и т. Д. (т. е. string.SomeMethod, в значительной степени гарантированно будет тем методом, который вам следует использовать, и он даст вам кое-что конкретное для Google.)


Методы расширения в стиле C # были бы хорошим решением.

  1. Они бесполезные функции.
  2. Они отображаются как функции-члены через intellisense.

Это должно позволить каждому делать именно то, что он хочет.

Похоже, что это может быть выполнено в самой IDE, а не требует каких-либо языковых изменений.

По сути, если интерпретатор выполняет какой-то вызов несуществующего члена, он может проверять заголовки на соответствие свободным функциям и динамически исправлять его, прежде чем передать его компилятору.

Нечто подобное можно сделать при загрузке данных intellisense.

Я понятия не имею, как это можно сделать для существующих функций, никакие массовые изменения не следует воспринимать легкомысленно, но для новых функций, использующих новый синтаксис, это не должно быть проблемой.

    namespace StringExt
    {
        std::string MyFunc(this std::string source);
    }

Это может быть использовано само по себе или в качестве члена std :: string, и IDE может обрабатывать всю тяжелую работу.

Конечно, это все еще оставляет проблему распространения методов по различным заголовкам, которая может быть решена различными способами.

  1. Какой-то заголовок расширения: string_ext, который может включать обычные методы.
  2. Хм ....

Сложнее решить проблему, не вызывая проблем ...

1 голос
/ 10 января 2010

Если область действия строки не совпадает с StringBox, вы можете получить segfaults:

StringBox foo() {
  string s("abc");
  return StringBox(s);
}

По крайней мере, предотвратить копирование объекта, объявив оператор присваивания и ctor private:

class StringBox {
  //...
  private:
    void operator=(const StringBox&);
    StringBox(const StringBox&);
};

РЕДАКТИРОВАТЬ: в отношении API, для предотвращения неожиданностей, я бы StringBox владел своей копией строки. Я могу думать о 2 способах сделать это:

  1. Скопируйте строку в член (не ссылку), получите результат позже - также как копию
  2. Доступ к вашей строке с помощью интеллектуального указателя подсчета ссылок, например std::tr1::shared_ptr или boost:shared_ptr, для предотвращения дополнительного копирования
0 голосов
/ 10 января 2010

Лучший способ - использовать шаблонные бесплатные функции. Следующим лучшим является частное наследование struct extended_str : private string, которое в C ++ 0x упрощается, когда вы можете using конструкторы. Частное наследование - это слишком много проблем и слишком рискованно, чтобы добавить некоторые алгоритмы. То, что вы делаете, слишком рискованно для всего.

Вы только что ввели нетривиальную структуру данных, чтобы выполнить изменение в пунктуации кода. Вы должны вручную создать и уничтожить Box для каждого string, и вам все равно нужно отличать ваши методы от нативных. Вы быстро устанете от этого соглашения.

0 голосов
/ 10 января 2010

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

Это немного нарушает объектно-ориентированную модель, но делает код намного более надежным - то есть, если вы изменяете свой класс строки, то это не так сильно влияет на другой код.

Следуйте рекомендуемым рекомендациям, они существуют по причине:)

...