2015-05-06 3 views
2

Я использую g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2 в проекте на C++. Я написал функцию, которая отчасти делает это:Хвост-рекурсия не происходит

template<typename T, T (*funct)(int) > 
multiset<T> Foo(const multiset<T>& bar, int iterations) { 
    if (iterations == 0) return bar; 
    multiset<T> copy_bar(bar); 

    T baz = funct(copy_bar.size()); 

    if (baz.attr > 0) 
     return Foo<T,funct>(copy_bar, 100); 
    else 
     return Foo<T,funct>(bar, iterations - 1);  
} 

я получал bad_alloc() exception так что я тестировал функцию с gdb и оказывается, что нет хвоста рекурсия происходит, что я ожидал, так как нет никаких заявлений после того, как return s.

ПРИМЕЧАНИЕ: Я попытался с -O2 флагом компиляции, но он не работает

+0

Каков вклад функции? Без этой функции трудно сказать, что не так – Matt

+0

@Matt Я думаю, что 'funct' в данном случае не имеет значения. Когда я запускаю 'gdb', в стеке есть только вызовы Foo, потому что вызывался' funct', а затем он возвращался. – Jcao02

+0

Что происходит, если вы назначаете переменные в свой оператор 'if/else' и имеете только один вызов' Foo' в конце? –

ответ

3

Ваша функция не хвостовой рекурсией, поскольку там еще предстоит сделать после рекурсивного вызова: Уничтожение copy_bar (который имеет нетривиальный деструктор) и, возможно, также baz (если тип T также имеет нетривиальный деструктор).

1

Я не думаю, что @celschk прав, но, скорее, @jxh в ответе, который он уничтожил, поэтому давайте снова его рассмотрим.

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

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

Чтобы преобразовать рекурсию в цикл, вам нужно создать дополнительную переменную за пределами цикла, чтобы удерживать значение multimap, превратить ссылку в указатель и обновить указатель на тот или иной объект в зависимости от перед переходом к началу цикла. Переменная baz не может быть использована для этого (т. Е. Ее нельзя вытащить за пределы цикла), поскольку каждый проход делает копию, и я представляю некоторые другие преобразования, которые вы не показывали в приведенном выше коде, поэтому вам действительно нужно создать дополнительную переменную , Компилятор не может создавать для вас новые переменные.

На данный момент, я должен признать, что да, проблема здесь в том, что для одной из ветвей copy_var должен быть уничтожен после рекурсия завершает (как ссылка на него передается вниз), так @celtschk не на 100% ошибочно ... но он, когда он указывает на baz в качестве еще одной потенциальной причины нарушения хвостовой рекурсии.

+0

Примечание: вы можете сделать это преобразование вручную: создать переменную, пустую, вне цикла; всякий раз, когда условие истинно, вы можете поменять содержимое переменной внутри цикла с переменной вне цикла (что довольно эффективно) и отрегулировать указатель. Если вы знаете, что будете выполнять несколько проходов и, следовательно, вам понадобится несколько копий, вы также можете принять значение с помощью non-const ref и предоставить фасад, который скопирует аргумент пользователя перед пересылкой на этот, а внутри внутри вы можете взорвать (обменивать) значение аргумента перед рекурсией с тем же аргументом, который вы получили. –

+0

Примечание2: Если код действительно такой, как вы показали его выше ... ну, вам не нужна копия аргумента для вызова 'size()', вы можете полностью удалить объект и сделать свою жизнь (и компилятор) Полегче. –

+0

Проблема с моим ответом заключается в том, что когда я тестировал его, всегда используя ложный случай (в этом случае одна и та же ссылка передается при каждом рекурсивном вызове), хвостовая рекурсия все еще не встречалась. Остается только код деструктора для 'copy_bar'. – jxh

2

Как указано @celtschk's answer, нетривиальные деструкторы не позволяют компилятору обрабатывать вызовы как по-настоящему хвостовые рекурсивные. Даже если ваша функция сводятся к следующему:

template<typename T, T (*funct)(int) > 
multiset<T> Foo(const multiset<T>& bar, int iterations) { 
    if (iterations == 0) return bar; 
    return Foo<T,funct>(bar, iterations - 1); 
} 

Это еще не хвостовая рекурсии, из-за неявные вызовы к нетривиальным конструкторам и деструкторам в результате вызова рекурсивной функции.

Однако следует отметить, что данная функция действительно становится хвостовой рекурсией с относительно небольшим изменением:

template<typename T, T (*funct)(int) > 
const multiset<T>& Foo(const multiset<T>& bar, int iterations) { 
    if (iterations == 0) return bar; 
    return Foo<T,funct>(bar, iterations - 1); 
} 

Voila! Теперь функция скомпилируется в цикл. Как мы можем добиться этого с помощью вашего исходного кода?

К сожалению, это немного сложно, потому что ваш код иногда возвращает копию, а иногда и исходный аргумент. Мы должны правильно разобраться с обоими случаями. Решение, представленное ниже, представляет собой вариацию идеи David, упомянутой в его комментариях к его ответу.

Предполагая, что вы хотите оставить свою оригинальную подпись Foo той же (и, поскольку она иногда возвращает копию, нет оснований полагать, что вы не захотите оставить подпись тем же), мы создаем вторичную версию Foo называется Foo2, который возвращает ссылку на объект результата, который является либо исходным параметром bar, либо одним локальным в вашем Foo. Кроме того, Foo создает место объекты держатель для копирования, вторичная копия (для переключения), и один для хранения результата funct() вызова:

template<typename T, T (*funct)(int) > 
multiset<T> Foo(const multiset<T>& bar, int iterations) { 
    multiset<T> copy_bar; 
    multiset<T> copy_alt; 
    T baz; 
    return Foo2<T, funct>(bar, iterations, copy_bar, copy_alt, baz); 
} 

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

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

template<typename T, T (*funct)(int) > 
const multiset<T>& Foo2(
     const multiset<T>& bar, int iterations, 
     multiset<T>& copy_bar, multiset<T>& copy_alt, T& baz) { 
    if (iterations == 0) return bar; 
    copy_bar = bar; 
    baz = funct(copy_bar.size()); 
    if (baz.attr > 0) { 
     return Foo2<T, funct>(copy_bar, 100, copy_alt, copy_bar, baz); 
    } else { 
     return Foo2<T, funct>(bar, --iterations, copy_bar, copy_alt, baz); 
    } 
} 

Обратите внимание, что только первоначальный вызов Foo платит штраф строительства и уничтожения локальных объектов, и Foo2 теперь полностью хвостовой рекурсией.

Смежные вопросы