Могу ли я позволить компилятору С++ решить, следует ли передавать по значению или передавать по ссылке? - программирование
Подтвердить что ты не робот

Могу ли я позволить компилятору С++ решить, следует ли передавать по значению или передавать по ссылке?

Посмотрите на этот гипотетический заголовочный файл:

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 по умолчанию.
4b9b3361

Ответ 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*), инварианты, налагаемые семантикой ссылок С++, позволяют компилятору просто подставлять значение в тело вашей функции, если это выигрыш. Если это не так, ваши наихудшие накладные расходы - это один разворот указателя на аргумент в прологе функции.