Как должен выглядеть интерфейс приема строк? - PullRequest
4 голосов
/ 09 января 2011

Это продолжение этого вопроса .Предположим, я пишу интерфейс C ++, который принимает или возвращает константную строку.Я могу использовать const char * строку с нулевым символом в конце:

void f(const char* str); // (1)

Другой способ - использовать std :: string:

void f(const string& str); // (2)

Также возможно написать перегрузкуи принимаю оба:

void f(const char* str); // (3)
void f(const string& str);

Или даже шаблон в сочетании с алгоритмами форсированной строки:

template<class Range> void f(const Range& str); // (4)

Мои мысли таковы:

  • (1)не C ++ ish и может быть менее эффективным, когда последующим операциям может понадобиться узнать длину строки.
  • (2) плохо, потому что теперь f("long very long C string"); вызывает конструкцию std :: string, которая включает выделение кучи,Если f использует эту строку просто для передачи ее в какой-то низкоуровневый интерфейс, который ожидает C-строку (например, fopen), то это просто пустая трата ресурсов.
  • (3) вызывает дублирование кода.Хотя один f может вызывать другой в зависимости от того, какая реализация наиболее эффективна.Однако мы не можем перегрузить на основе возвращаемого типа, как в случае с std :: exception :: what (), который возвращает const char *.
  • (4), не работает с отдельной компиляцией и может вызватьеще больше раздувать код.
  • Выбор между (1) и (2) в зависимости от того, что требуется для реализации, хорошо, утечка деталей реализации в интерфейс.

Вопрос в том, что являетсяпредпочтительный способ?Есть ли какое-то единственное руководство, которому я могу следовать?Какой у вас опыт?

Редактировать: Существует также пятый вариант:

void f(boost::iterator_range<const char*> str); // (5)

, который имеет плюсы (1) (не нужно строитьстроковый объект) и (2) (размер строки явно передается функции).

Ответы [ 8 ]

7 голосов
/ 09 января 2011

Если вы имеете дело с чистой базой кода C ++, то я бы остановился на # 2 и не беспокоился о вызывающих функциях, которые не используют ее с std :: string, пока не возникнет проблема.Как всегда, не беспокойтесь об оптимизации, если нет проблем.Сделайте ваш код чистым, легко читаемым и легко расширяемым.

4 голосов
/ 09 января 2011

Существует одно правило, которому вы можете следовать: используйте (2), если у вас нет веских причин не делать этого.

A const char* str, так как параметр не делает явным, какие операции разрешено выполнятьна str.Как часто его можно увеличивать до появления ошибок?Это указатель на char, массив char с или строку C (т.е. массив с нулевым символом в конце char)?

3 голосов
/ 10 января 2011

У меня нет ни одного сложного предпочтения. В зависимости от обстоятельств я чередую большинство ваших примеров.

Другой вариант, который я иногда использую, похож на ваш Range пример, но с использованием простых старых диапазонов итераторов:

template <typename Iter>
void f(Iter first, Iter last);

, у которого есть приятное свойство: он легко работает как со строками в стиле C (и позволяет вызываемому абоненту определять длину строки в постоянном времени), так и с std::string.

Если шаблоны проблематичны (возможно, потому что я не хочу, чтобы функция была определена в заголовке), я иногда делаю то же самое, но используя char* в качестве итераторов:

void f(const char* first, const char* last);

Опять же, его можно тривиально использовать как с C-строками, так и с C ++ std::string (насколько я помню, C ++ 03 явно не требует, чтобы строки были смежными, но каждая известная мне реализация использует смежные строки, и я верю, что C ++ 0x явно потребует это).

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

Недостатком является то, что вы получите дополнительный параметр.

К сожалению, обработка строк на самом деле не самая сильная сторона C ++, поэтому я не думаю, что есть единственный «лучший» подход. Но пара итераторов - один из нескольких подходов, которые я склонен использовать.

2 голосов
/ 09 января 2011

Для получения параметра я бы выбрал то, что является самым простым и часто это const char*. Это работает со строковыми литералами с нулевой стоимостью, и извлечение const char* из чего-то, хранящегося в std:string, также обычно очень дешево.

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

Только если бы я действительно хотел использовать const функции интерфейса std::string внутри функции, у меня было бы const std::string& в самом интерфейсе, и я не уверен, что простого использования size() было бы достаточно обоснование.

Во многих проектах, к лучшему или к худшему, часто используются альтернативные классы строк. Многие из них, такие как std::string, предоставляют дешевый доступ к const char* с нулевым символом в конце; преобразование в std::string требует копии. Требование const std::string& в интерфейсе диктует стратегию хранения, даже если внутренняя часть функции не должна указывать это. Я считаю, что это нежелательно, так же как принятие const shared_ptr<X>& определяет стратегию хранения, тогда как принятие X&, если возможно, позволяет вызывающей стороне использовать любую стратегию хранения для переданного объекта.

Недостатки const char* заключаются в том, что чисто с точки зрения интерфейса он не обеспечивает ненулевое значение (хотя очень редко разница между нулевым параметром и пустой строкой используется в некоторых интерфейсах - это может ' это может быть сделано с std::string), а const char* может быть адресом только одного символа. На практике, однако, использование const char* для передачи строки настолько распространено, что я считаю, что упоминание этого как негатива является довольно тривиальной задачей. Другие проблемы, такие как, является ли кодировка символов, указанная в документации по интерфейсу (применима как к std::string, так и к const char*), гораздо важнее и, вероятно, вызовет больше работы.

0 голосов
/ 09 января 2011

Использовать (2).

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

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

(2) четко сообщает, что функция принимает, и имеет правильные ограничения.

Конечно, все 5 улучшений по сравнению с foo(char*) с которым я столкнулся больше, чем хотел бы упомянуть.

0 голосов
/ 09 января 2011

Я бы выбрал void f(const string& str), если тело функции не выполняет char -анализ;означает, что это не относится к char* из str.

0 голосов
/ 09 января 2011

Ответ должен сильно зависеть от того, что вы собираетесь делать в f.Если вам нужно выполнить некоторую сложную обработку со строкой, подход 2 имеет смысл, если вам просто нужно перейти к некоторым другим функциям, а затем выбрать на основе этих других функций (скажем, для аргументов вы открываете файл - что будетимеет больше смысла?;))

0 голосов
/ 09 января 2011

Также можно написать перегрузить и принять оба:

void f(const string& str) уже принимает оба из-за неявного преобразования из const char* в std::string. Так что # 3 имеет небольшое преимущество перед # 2.

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