2012-01-26 3 views
12

Я хочу написать шаблонную функцию, которая делает что-то с std::stack<T> и экземпляром T, например:Будет ли хороший компилятор C++ оптимизировать ссылку?

template<class StackType> inline 
bool some_func(StackType const &s, typename StackType::value_type const &v) { 
    // ... 
} 

Причина проходит v по ссылке, конечно, оптимизировать для случая, когда StackType::value_type является struct или class, а не копировать весь объект по значению.

Однако, если StackType::value_type является «простым» типом, например int, то, конечно, лучше просто передать его по значению.

Вопрос такой: для типа int, который станет int const& как формальный аргумент в вышеуказанной функции, будет ли компилятор оптимизировать ссылку и просто передать ее по значению?

+2

Это будет плохой компилятор. – lapk

+0

Если компилятор действительно решает встроить функцию, она, безусловно, сделает самую эффективную вещь, независимо от того, как вы ее определяете. – enobayram

ответ

6

Хотя я на самом деле не тестировал никаких компиляторов для этого, я сомневаюсь. Вы предполагаете, что передача ссылки const неразличима от просто передачи значения, но это не так, и компилятор не должен предполагать, что это так.

Поскольку ссылка const, ваша функция не может изменить значение через нее, но другие части кода могут иметь доступ к исходной (неконстантной) переменной и могут ее модифицировать. Например, ваша функция может вызывать некоторую другую функцию, которая изменяет ее, например, или может выполняться другой поток, выполняющийся одновременно.

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

Возможно, вас заинтересует библиотека Boost call traits. Он предоставляет тип шаблона call_traits<T>::param_type, который является ссылкой на const для «больших» типов, которые вы не хотите копировать, и значения для «малых» типов, где копия будет более эффективной. В принципе, то, что вы хотите, чтобы компилятор выполнял неявно, вы можете явно указать свой код.

+3

Вы не прав о потоках. Оптимизатор компилятора часто разбивает код, который работает с плохими «незакрепленными» (наивными) структурами данных. Компилятор doest должен думать о других потоках! –

+0

Кроме того, простые функции вообще не вызывают другие функции –

+0

@AlexandrPriymak, вопрос не указывает, какой код находится в функции, поэтому может быть и тот, который вызывает другие функции. И C++ 11 включает в себя модель памяти с поддержкой параллелизма и стандартизованные средства потоковой передачи, поэтому компилятор C++ 11 должен учитывать, что могут делать другие потоки, даже если компилятор C++ 98 этого не делает. – Wyzard

6

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

Для не-встроенных вызовов функций ссылка, вероятно, будет реализована как указатель, а не по значению.

3

Это MSVC2010 64bit компилятор, полная оптимизация:

int foo(int i) 
{ 
int a = i + i; 

return (a); 
} 
//Disassembly: 
//00000001`3ff11c50 8d0409   lea  eax,[rcx+rcx] 
//00000001`3ff11c53 c3    ret 

int bar(int const & i) 
{ 
int a = i + i; 

return (a); 
} 
//Disassembly: 
//00000001`3ff11c10 8b01   mov  eax,dword ptr [rcx] 
//00000001`3ff11c12 03c0   add  eax,eax 
//00000001`3ff11c14 c3    ret 

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

Передача экземпляров «малых» классов (скажем, класс содержит только элемент данных int и тривиальный конструктор, побитовое копирование-конструктор и т. Д.) Быстрее по значению. Компилятор будет оптимизировать его, по сути, просто передав копию int. Таким образом, вы можете перегружать свою функцию, как указал Уазард, используя некоторые черты типа.Нечто подобное:

template< typename PType > 
PType Func(typename std::enable_if< (sizeof(PType) > sizeof(size_t)), PType const & >::type f) 
{ 
//large things: reference 
} 

template< typename PType > 
PType Func(typename std::enable_if< (sizeof(PType) <= sizeof(size_t)), PType >::type f) 
{ 
//small things: value 
} 
+1

Обратите внимание, что ваш экспериментальный анализ предназначен для «нормальных» функций, а не для шаблонных функций. Это критическое различие; нормальная функция не может (в общем) иметь свой список аргументов, скорректированный с помощью оптимизации компилятора, тогда как шаблонная функция обязательно является неотъемлемой и, следовательно, может. –

+0

@BrooksMoses Функциональная вставка не зависит от того, что это шаблонная или нетемпликационная функция. Просто тот факт, что в каждом компиляционном модуле, в котором он использовался, имеет определение шаблонных функций, не гарантирует его вложения вообще ... Если мои «экспериментальные» функции одновременно объявлены и определены в одном блоке компиляции, они будут включены. И код будет таким же. И наоборот, даже «простые» шаблонные функции могут не быть встроены. – lapk

+0

Да, я упрощен. Тем не менее, если ваши «экспериментальные» функции объявлены и определены в одном блоке компиляции, но объявлены таким образом, что они экспортируются из этого функционального блока, то они не могут быть встроены. И вы не указали, что они либо статичны, либо что вы составляете автономную целую программу, а это означает, что они были обязательно экспортированы. –

6

Я смотрю в GCC вариантов оптимизации здесь http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

А на самом деле есть вариант для вашего случая:

-fipa-SRA

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

Enabled на уровне -O2, -O3 и -OS

Насколько я знаю, -O2 является обычным вариантом для выпуска сборки на Linux.

Итак, короткий ответ: один из хорошего компилятора делает