Должен ли я вернуть std :: strings? - PullRequest
65 голосов
/ 23 июня 2009

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

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

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

Спасибо.

Ответы [ 12 ]

63 голосов
/ 23 июня 2009

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

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

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

14 голосов
/ 23 июня 2009

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

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

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

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

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

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

12 голосов
/ 23 июня 2009

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

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

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

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

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

10 голосов
/ 23 июня 2009

Похоже, хорошая идея.

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

Помните: " Преждевременная оптимизация - корень всего зла "

6 голосов
/ 23 июня 2009

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

5 голосов
/ 23 июня 2009

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

4 голосов
/ 23 июня 2009

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

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

3 голосов
/ 23 июня 2009

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

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

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

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

Стандартный текст (C ++ 03) для этого читает (12.2):

[Начать стандартную цитату]

Временные шкалы типа класса создаются в различных контекстах: привязка значения r к ссылке (8.5.3), возвращение значения r (6.6.3), преобразование, создающее значение r (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), чтобы избежать нежелательного наложения псевдонима а. ]

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

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

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

1 голос
/ 23 июня 2009

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

1 голос
/ 23 июня 2009

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

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

...