2013-05-06 3 views
7

У меня есть шаблонные функции в моем проекте Xcode C++ 11, а некоторые из них имеют специализации. Тем не менее, я обнаружил, что специализации только вызываются в отладочных сборках; если я создам релиз, они будут проигнорированы.Почему моя специализированная функция шаблона вызывается только в отладочных сборках?

Я успешно создал очень простой пример:

special.h

#include <cstdio> 

struct special 
{ 
    template<typename T> 
    void call(const T&) { puts("not so special"); } 
}; 

special.cpp

#include "special.h" 
#include <string> 

template<> 
void special::call(const std::string&) { puts("very special"); } 

main.cpp

#include "special.h" 
#include <string> 

int main() 
{ 
    std::string str = "hello world"; 
    special s; 
    s.call(123); 
    s.call(str); 
} 

You can download the project (до тех пор, пока, как минимум, этим летом 2013 года), чтобы воспроизвести проблему, если вы не хотите ее создавать самостоятельно. Сначала запустите проект с конфигурацией отладки, а затем запустите его снова в выпуске. Вывод, который я ожидаю:

не настолько особенным
очень специальный

И это действительно то, что я получаю с конфигурацией Debug сборки. Тем не менее, с версией, я получаю это:

не так специальный
не настолько особенный

Это означает специализированную реализацию special::call в special.cpp был проигнорирован.

Почему результат непоследовательный? Что делать, чтобы гарантировать, что специализированная функция вызывается в версиях сборки?

+0

Поместите его в .h вместо .cpp? – Pubby

+0

special.cpp связан в обоих случаях? – ForEveR

+0

Да, special.cpp скомпилирован и связан в обоих случаях. Если я поместил специализацию в файл заголовка, он будет работать, если файл включен только в один .cpp-файл, но в остальном я получаю дубликат символьной ошибки. – zneak

ответ

11

Ваша программа имеет UB. Явная специализация или, по крайней мере, ее декларация должны быть видны перед использованием. [Temp.expl.spec] § 6:

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

Добавить объявление в :

template<> 
void special::call(const std::string&); 

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

Кроме того, будьте осторожны, что специализация шаблонов функций имеет довольно специфическое поведение, и обычно лучше использовать перегрузки, чем специализации. См. Herb Sutter's article.

8

Вы нарушили одно правило определения (ODR). Так что же происходит именно так? В main.cpp нет специализации, известной для special::call<string>. Поэтому компилятор генерирует экземпляр шаблона в этот блок перевода (TU), который выводит «не столь особенный». В special.cpp существует полная специализация, объявленная и определенная, поэтому компилятор помещает это определение в другую единицу перевода. Таким образом, у вас есть два разных определения одной и той же функции в двух разных единицах перевода, что является нарушением ODR, что означает, что это неопределенное поведение.

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

На практике I Угадайте происходит следующее: При связывании сборки отладки компоновщик видит тот же символ, определенный дважды в двух TU, который разрешен только для шаблонов и встроенных функций. Из-за ODR можно предположить, что оба определения эквивалентны и выбирают один из special.cpp, поэтому вы по совпадению получаете поведение, которое вы ожидаете.
Во время сборки выпуска компилятор строит вызов special::call<string> во время компиляции main.cpp, поэтому вы получаете единственное поведение, наблюдаемое в этом TU: «не так уж и особенный».

Итак, как вы можете это исправить?
Для того, чтобы иметь только одно определение для этой специализации, вы должны определить его в один БПД, как вы делали, но вы должны объявить, что есть полная специализация в любой другой ТУ, то есть заявить, что по специальности существует в заголовке :

// in special.h 
template<> 
void special::call(const std::string&); 

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

// in special.h 
template<> 
inline void special::call(const std::string&) 
{ 
    puts("very special"); 
} 
+0

+1 для полного объяснения того, что происходит, а не только требуемой коррекции. Что касается выбора функции из 'specials.cpp', я бы предположил, что это связано с тем, что файлы' .o' передаются в компоновщик; я подозреваю, что 'specials.o' передается до' main.o'. –

+0

Я предполагаю, что это очень специфическая часть реализации. Это будет зависеть от порядка, в котором линкер * обрабатывает * объектные файлы, что в свою очередь зависит от порядка, в котором объектные файлы передаются ему через командную строку. И это может зависеть от того, как компоновщик хранит и просматривает символы, которые он находит в двух TU. Это может быть что угодно, но я полагаю, что наиболее очевидными были бы первые переданные объектные файлы, первый обработанный, и первый раз, когда найденный символ является единственным, который хранится и вызывается. –

+0

О, вы правы, это похоже на специфику реализации, но поскольку видимо поведение * стабильно *, возможно, есть объяснение. –

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