2011-01-09 2 views
3

Это продолжение this question. Предположим, что я пишу интерфейс C++, который принимает или возвращает строку const. Я могу использовать константный символ * завершается нулем строку:Как должен выглядеть интерфейс приема строк?

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

Другой способ будет использовать зЬй :: строка:

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

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

Edit: Существует также пятый вариант:

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

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

+0

в случае (2) не будет распределения кучи. строка будет построена в стеке –

+0

@nice: справа, std :: string сама выделяется в стеке. Но если ваша строка достаточно длинная или ваша реализация не использует оптимизацию с короткой строкой, то std :: string будет выделять ее хранилище в куче. – ybungalobill

+0

Я думаю, что распределение кучи произойдет только тогда, когда std :: string copy contructor называется –

ответ

1

Для взятия параметра я бы пошел с самым простым и часто это 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

Я всегда предпочитал простой const char * по тем же причинам, что и вы. Btw, есть пятый вариант: void f (boost :: iterator_range str), который не диктует стратегию хранения и при этом эффективен как std :: string. Я просто не проверял, насколько чистым становится код. – ybungalobill

+2

@ybungalobill: вы добавили зависимость от повышения и для многих людей, что является нетривиальной проблемой. –

+0

это просто идея. Это может быть что угодно, например std :: pair или, возможно, реализовать свою собственную облегченную оболочку. – ybungalobill

7

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

+0

Но почему вы предпочитаете 2, например? 1? Это не делает его чище, легче читать или проще продлить! – ybungalobill

+1

@ybungalobill: Потому что, если я пишу C++, я бы предпочел иметь дело с конструкциями C++, если у меня не возникнут проблемы с производительностью, которые мне нужно начинать с адресации. –

+3

@ybungalobill 'const char * str' является указателем * на символ * по определению. Это * строка * только по соглашению. Вот почему в C++ 2 чище. – Oswald

4

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

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

+0

-1 потому что нет объяснений. – ybungalobill

+1

Вы также можете следовать руководству: используйте (1), если у вас нет очень веских причин. Можете ли вы обосновать свое руководство альтернативой? –

+1

Все действующие проблемы; почему бы не поставить их в свой ответ? –

0

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

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

+1

Он избегает преобразования. Это позволяет реализации решить, какая версия лучше. – ybungalobill

0

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

+0

Это утечка детали реализации. Именно этого я и хочу избежать. – ybungalobill

+0

... действительно? как? что принимает 'const char *' говорит вам выше 'const std :: string &' - или что скрывает? – Nim

+0

@Nim, предположим, что завтра я изменю свою реализацию, так что предпочтительнее другой. По вашему мнению, мне нужно изменить свой интерфейс. Это утечка детали реализации: «эта функция использует XXX внутренне, поэтому каждый раз, когда она изменится с XXX на YYY, интерфейс изменится соответствующим образом». – ybungalobill

0

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

+1

Что именно вы подразумеваете под символом 'char'-anaysis? Как «std :: string» более или менее подходит для анализа его составного символа 'char', чем' const char * '? –

+0

@Charles: если тело функции работает с символами строки (больше похоже на синтаксический анализ), то зачем же начинать с 'string'? – Nawaz

+0

Предполагаю, что вы имеете в виду работу в смысле только для чтения, поскольку обе альтернативы в вопросе доступны только для чтения; не так ли легко читать символы 'std :: string' как массив' char' (переданный через 'const char *') - действительно, вы можете использовать тот же синтаксис '[]'? В самом деле, зачем вообще передавать параметр, если вы не собираетесь просматривать его содержимое? –

0

Использование (2).

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

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

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

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

+0

"строка должна быть создана в какой-то момент" нет, это не так, как в f (...) {cout << str; }, строки не создаются. »(2) четко сообщает, что функция принимает« это не так ». Функция ожидает случайную последовательность доступа символов, она не обязательно ожидает объект std :: string. Я не понимаю, почему мне нужно указать в интерфейсе, что эта последовательность должна принадлежать std :: string. – ybungalobill

+0

Вы искали ответ или аргумент? – JohnMcG

+0

Моей первой точкой в ​​какой-то момент должна быть создана структура для хранения строки в памяти. Если вы не хотите, чтобы это был объект, тогда придерживайтесь C. – JohnMcG

3

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

Другого варианта я иногда использую похож на ваш 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 будет явно требовать Это).

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

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

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

+0

+1. преимущество единственного диапазона параметров заключается в том, что он позволяет автоматически конвертировать из обоих: std :: string и C-строк, поэтому код пользователя остается таким же простым, как и раньше: f («привет»). Это невозможно с двумя параметрами. Интересно, почему стандарт C++ 0x ничего не делает в этом направлении для fstream :: open ... – ybungalobill

+0

Проблема с единственным диапазоном параметров заключается в том, что вы получаете зависимость от Boost.Range, если хотите, чтобы он * Just Work * с C-строками. Но вы правы, синтаксис для этого, безусловно, более удобен. – jalf

+0

это не обязательно должен быть диапазон повышения. Диапазон Boost - это просто пример, он также не будет обеспечивать автоматическое преобразование из std :: string, поэтому определенный производный тип будет определен в любом случае. Однако, с другой стороны, есть еще одна проблема такого подхода: в отличие от (1), (5) не имеет семантики с нулевым окончанием. Это означает, что он не полностью решает проблему, когда вы используете ее с низкоуровневой функцией, ожидающей нулевой завершающей строки. В таком случае вам все равно нужно создать копию с нулевым завершением. есть идеи? – ybungalobill

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