Должен ли я возвращать std::strings?

Я пытаюсь использовать std::string вместо char*, когда это возможно, но я беспокоюсь, что могу слишком сильно снизить производительность. Является ли это хорошим способом возврата строк (без проверки ошибок для краткости)?

std::string linux_settings_provider::get_home_folder() {
    return std::string(getenv("HOME"));
}

Кроме того, связанный с этим вопрос: при приеме строк в качестве параметров я должен получать их как const std::string& или const char*?

Спасибо.


person Pedro d'Aquino    schedule 23.06.2009    source источник
comment
Придирка: getenv() может вернуть NULL, если переменная не существует, что приведет к тому, что конструктор std::string выдаст исключение.   -  person Tom    schedule 23.06.2009
comment
Спасибо. Производственный код проверяет наличие NULL, но для ясности я опустил его.   -  person Pedro d'Aquino    schedule 23.06.2009
comment
Все любят переосмысливать код примера SO: P   -  person Justin Meiners    schedule 31.12.2012


Ответы (12)


Вернуть строку.

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

Потребовалось много лет, чтобы получить хорошую абстракцию строк в C++. Я не верю, что Бьерн Страуструп, столь известный своим консервативным изречением «плати только за то, что используешь», допустил бы в язык явного убийцу производительности. Высшая абстракция — это хорошо.

person duffymo    schedule 23.06.2009
comment
Спасибо. Я немного боялся, что это сочтут плохой практикой, но я рад видеть, что это не так :-) - person Pedro d'Aquino; 23.06.2009
comment
помните, что вы всегда можете использовать ссылки, где это уместно, чтобы избежать ненужных копий. я стараюсь иметь входные параметры как const std::string& где это возможно - person ShoeLace; 23.06.2009
comment
Потребовалось много лет, чтобы получить хорошую абстракцию строк в C++. ИМХО все равно хреново. - person Johan Kotlinski; 23.06.2009
comment
Как так? Все еще улучшение по сравнению с char *. - person duffymo; 23.06.2009
comment
Я не думаю, что позволять лучшему быть врагом хорошего — мудрая стратегия. Ожидание идеального программного обеспечения не является ответом. - person duffymo; 23.06.2009
comment
Иногда я принимаю const char* в качестве параметра, но возвращаю std::string. Для меня это предпочтение, и, кажется, код немного упрощает работу, хотя я не могу придумать, почему вы не можете обернуть свой const char* в std::string и передать его как параметр. Я думаю, вам решать, как вы хотите это сделать (хотя возвращать const char* обычно неразумно, IMO). - person David Peterson; 25.12.2012

Верните строку, как все говорят.

при приеме строк в качестве параметров я должен получать их как const std::string& или const char*?

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

Неконстантные ссылочные параметры являются спорными, потому что из вызывающего кода (без хорошей IDE) вы не можете сразу увидеть, передаются ли они по значению или по ссылке, а разница важна. Так что код может быть непонятным. Для константных параметров это неприменимо. Люди, читающие вызывающий код, обычно могут просто предположить, что это не их проблема, поэтому им нужно лишь время от времени проверять подпись.

В случае, когда вы собираетесь получить копию аргумента функции, ваша общая политика должна состоять в том, чтобы принимать аргумент по значению. Тогда у вас уже есть копия, которую вы можете использовать, и если бы вы скопировали ее в какое-то конкретное место (например, член данных), вы можете переместить ее (в С++ 11) или поменять местами (в С++ 03) на получить его там. Это дает компилятору наилучшую возможность оптимизировать случаи, когда вызывающий объект передает временный объект.

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

person Steve Jessop    schedule 23.06.2009

Стоимость копирования строк по значению зависит от реализации STL, с которой вы работаете:

  • std::string в MSVC использует оптимизацию коротких строк, так что короткие строки (‹ 16 символов iirc) не требуют выделения памяти (они хранятся в самом std::string), а более длинные требуют выделения кучи. каждый раз, когда строка копируется.

  • std::string в GCC использует реализацию с подсчетом ссылок: при построении std::string из char* выделение кучи выполняется каждый раз, но при передаче по значению в функцию счетчик ссылок просто увеличивается, избегая выделение памяти.

В общем, вам лучше просто забыть об этом и вернуть std::strings по значению, если только вы не делаете это тысячи раз в секунду.

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

person jskinner    schedule 23.06.2009
comment
Разве это не означает, что мне лучше принять const char*? Если у моего клиента есть std::string, он может использовать c_str(), что, как вы сказали, не требует больших затрат. С другой стороны, если у него есть char*, он вынужден создавать std::string. - person Pedro d'Aquino; 23.06.2009
comment
Брайан: GCC, безусловно, использует реализацию строк с подсчетом ссылок, почитайте, например, /usr/include/c++/4.3/bits/basic_string.h. - person jskinner; 24.06.2009
comment
Педро: Если вы пишете функцию, которой нужен только const char*, то да, вам определенно лучше принять const char*. Если функции это нужно как std::string, то лучше так. Мой комментарий больше относился к случаям, когда вы не знаете, что вам нужно (например, при написании класса интерфейса). - person jskinner; 24.06.2009
comment
@Brian - RTFCode, это просто как день. GCC по-прежнему использует подсчет ссылок. - person Tom; 24.06.2009
comment
Вау, я был совершенно неправ. Прости за это. Я помню, как читал подробную статью о неудачах строк с подсчетом ссылок и о том, что на самом деле более эффективно использовать решение с подсчетом ссылок без ссылок. Должно быть, мне все это приснилось. - person Brian Neal; 25.06.2009
comment
Похоже, что реализация std::string в gcc изменилась в версии 5, так что она больше не использует подсчет ссылок и использует оптимизацию коротких строк, как MSVC. Найдите std::string в gcc.gnu.org/gcc-5/changes.html. . - person Andrew Bainbridge; 26.07.2015

Кажется, это хорошая идея.

Если это не часть программного обеспечения реального времени (например, игра), а обычное приложение, все должно быть в порядке.

Помните: «Преждевременная оптимизация — корень всех зол».

person kostia    schedule 23.06.2009

Человеку свойственно беспокоиться о производительности, особенно когда язык программирования поддерживает низкоуровневую оптимизацию. Что мы, как программисты, не должны забывать, так это то, что производительность программы — это лишь одна из многих вещей, которые мы можем оптимизировать и которыми мы можем восхищаться. Помимо скорости программы, мы можем найти красоту в собственном исполнении. Мы можем свести к минимуму наши усилия, пытаясь добиться максимального визуального вывода и интерактивности пользовательского интерфейса. Как вы думаете, может ли это быть большей мотивацией, чем беспокойство о битах и ​​циклах в долгосрочной перспективе... Так что да, верните строку: s. Они минимизируют размер вашего кода и ваши усилия, а также делают объем работы менее угнетающим.

person AareP    schedule 23.06.2009

В вашем случае будет выполнена оптимизация возвращаемого значения, поэтому std::string не будет скопирован.

person Kirill V. Lyadvinsky    schedule 23.06.2009
comment
Это не правда. std::string будет динамически выделять буфер и копировать всю строку, и оптимизация возвращаемого значения здесь не поможет. Однако он все равно должен использовать std::string. После проверки того, что getenv() не вернул NULL, то есть! - person Tom; 23.06.2009
comment
Одно выделение будет реально. Я имею в виду, что бы не копировалась сама строка. - person Kirill V. Lyadvinsky; 23.06.2009
comment
+1: Вы правы. Без RVO пришлось бы выделять два буфера и копировать между ними. - person James Hopkin; 23.06.2009

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

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

person Hans Malherbe    schedule 23.06.2009
comment
Для этого вам нужно сделать гораздо больше, чем просто избегать возвращаемых типов C++... вам нужно полностью упростить весь код C++, чтобы действительно быть безопасным, и в этот момент вы собираетесь создать оболочку C в любом случае поверх вашей существующей кодовой базы из-за характера объявлений классов. - person Tom; 24.06.2009

Я согласен с другими плакатами, что вы должны использовать строку.

Но знайте, что в зависимости от того, насколько агрессивно ваш компилятор оптимизирует временные файлы, у вас, вероятно, будут дополнительные накладные расходы (из-за использования динамического массива символов). (Примечание: хорошая новость заключается в том, что в C++0a разумное использование ссылок rvalue не потребует оптимизации компилятора для повышения эффективности — и программисты смогут дать некоторые дополнительные гарантии производительности своего кода, не полагаясь на качество компилятор.)

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

Кто-то упомянул, что оптимизация возвращаемого значения (RVO) здесь не имеет значения - я не согласен.

Стандартный текст (С++ 03) по этому поводу гласит (12.2):

[Начало стандартной цитаты]

Временные типы класса создаются в различных контекстах: привязка rvalue к ссылке (8.5.3), возврат rvalue (6.6.3), преобразование, создающее rvalue (4.1, 5.2.9, 5.2.11, 5.4) , создание исключения (15.1), вход в обработчик (15.3) и некоторые инициализации (8.5). [Примечание: время жизни объектов-исключений описано в 15.1. ] Даже когда создание временного объекта избегается (12.8), все семантические ограничения должны соблюдаться, как если бы временный объект был создан. [Пример: даже если конструктор копирования не вызывается, все семантические ограничения, такие как доступность (пункт 11), должны быть удовлетворены. ]

 [Example:  
struct X {
  X(int);
  X(const X&);
  ˜X();
};

X f(X);

void g()
{
  X a(1);
  X b = f(X(2));
  a = f(a);
}

Здесь реализация может использовать временный объект для создания X(2) перед передачей его в f() с помощью конструктора копирования X; в качестве альтернативы, X(2) может быть создан в пространстве, используемом для хранения аргумента. Кроме того, можно использовать временное хранилище для хранения результата f(X(2)) перед его копированием в b с помощью конструктора копирования X; в качестве альтернативы результат f() может быть построен в b. С другой стороны, выражение a=f(a) требует временного выражения либо для аргумента a, либо для результата f(a), чтобы избежать нежелательного наложения алиасинга a. ]

[Конец стандартной цитаты]

По сути, текст выше говорит о том, что вы можете полагаться на RVO в ситуациях инициализации, но не в ситуациях присваивания. Причина в том, что когда вы инициализируете объект, то, чем вы его инициализируете, никак не может быть псевдонимом самого объекта (поэтому вы никогда не выполняете самопроверку в конструкторе копирования), но когда вы делаете задание, это может.

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

person Faisal Vali    schedule 23.06.2009

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

person Brian    schedule 23.06.2009

Я согласен с @duffymo. Не оптимизируйте, пока не измерите, это вдвойне верно при микрооптимизации. И всегда: измерьте до и после оптимизации, чтобы увидеть, действительно ли вы изменили ситуацию к лучшему.

person JesperE    schedule 23.06.2009

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

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

person Gab Royer    schedule 23.06.2009

Если вы передаете строку, на которую указывает ссылка, и работаете с этой строкой, вам не нужно ничего возвращать. ;)

person Partial    schedule 23.06.2009