Могу ли я позволить компилятору С++ решить, следует ли передавать по значению или передавать по ссылке?
Посмотрите на этот гипотетический заголовочный файл:
template <class T>
class HungryHippo {
public:
void ingest(const T& object);
private:
...
}
Теперь, для a HungryHippo<string>
, имеет смысл, что вы захотите ingest
ссылки на строки - копирование строки может быть очень дорогостоящим! Но при a HungryHippo<int>
это имеет меньшее значение. Передача int
напрямую может быть действительно дешевой (большинство компиляторов сделает это в регистре), но передача ссылки на int
- лишний ненужный уровень косвенности. Это относится также к возвращаемым значениям.
Есть ли способ предложить компилятору "эй, я не собираюсь изменять аргумент, поэтому вы решаете, следует ли передавать по значению или по ссылке, в зависимости от того, что, по вашему мнению, лучше"?
Некоторые вещи, которые могут иметь значение:
- Я могу подделать этот эффект вручную, написав
template <class T, bool PassByValue> class HungryHippo
, а затем специализируясь на PassByValue
. Если бы я хотел получить действительно фантазию, я мог бы даже сделать вывод PassByValue
на основе sizeof(T)
и std::is_trivially_copyable<T>
. В любом случае, это большая часть дополнительной работы, когда реализации будут выглядеть примерно одинаково, и я подозреваю, что компилятор может намного лучше решить, передавать ли значение по своему усмотрению, чем я могу.
-
Проект libС++, похоже, решает это, встраивая множество функций, поэтому компилятор может сделать выбор на один уровень вверх, но в этом случае скажем реализация ingest
довольно сложна и не стоит вставлять. Как объясняется в комментариях, все функции шаблона inline
по умолчанию.
Ответы
Ответ 1
Заголовок boost::call_traits
касается именно этой проблемы. Проверьте здесь.
В частности, опция call_traits<T>::param_type
включает следующее описание:
Если T
- небольшой встроенный тип или указатель, то param_type
определяется как T const
вместо T const&
. Это может улучшить способность компилятор для оптимизации циклов в теле функции, если они зависят по переданному параметру семантика переданного параметра в противном случае без изменений (требуется частичная специализация).
В вашем случае вы можете определить ingest
следующим образом:
template <class T>
class HungryHippo {
public:
void ingest(call_traits<T>::param_type object);
// "object" will be passed-by-value for small
// built-in types, but passed as a const reference
// otherwise
private:
...
};
Будет ли это действительно иметь большое значение в вашей реальной комбинации кода/компилятора, я не уверен. Как всегда, вам нужно будет запустить некоторые фактические тесты и посмотреть, что произойдет...
Ответ 2
В то время как трюки, такие как boost упомянутые call_traits<T>
, делают то, что они утверждают в этом случае, я думаю, вы предполагаете, что компилятор еще не делает эту оптимизацию в наиболее важных случаях. В конце концов, это тривиально. Если вы принимаете const T&
и sizeof(T) <= sizeof(void*)
, инварианты, налагаемые семантикой ссылок С++, позволяют компилятору просто подставлять значение в тело вашей функции, если это выигрыш. Если это не так, ваши наихудшие накладные расходы - это один разворот указателя на аргумент в прологе функции.