我在C ++ 11 Xcode项目中有模板化函数,其中一些有专门化。但是,我发现只在调试版本中调用了特化;如果我在发布中构建,则会被忽略。
我已经成功创建了一个非常简单的例子:
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构建配置中得到的。但是,有了Release,我明白了:
没那么特别 没那么特别
这意味着在special.cpp中special::call
的专门实现被忽略了。
为什么结果不一致?我该怎么做才能确保在发布版本中调用专用函数?
答案 0 :(得分:11)
你的程序有UB。在使用之前,必须显示明确的特化或至少其声明。 [temp.expl.spec]§6:
如果是模板,成员模板或类模板的成员 明确专业化然后宣布专业化 在第一次使用该特化之前会导致一个 隐式实例化发生在每个翻译单元中 发生了这种用途;无需诊断。
将此声明添加到special.h
:
template<>
void special::call(const std::string&);
或者,您可以将专业本身放入标题中。但是,由于专业化不再是模板,它遵循正常的功能规则,如果放在标题中,则必须标记为inline
。
另外,请注意,函数模板特化具有相当特定的行为,并且通常使用重载比使用特殊化更好。有关详细信息,请参阅Herb Sutter's article。
答案 1 :(得分:8)
您违反了一个定义规则(ODR)。那究竟发生了什么?在main.cpp
中,special::call<string>
没有专业知识。因此,编译器生成模板到该转换单元(TU)的实例化,该转换单元输出“不那么特殊”。在special.cpp
中,声明并定义了一个完整的特化,因此编译器将该定义放入另一个转换单元中。因此,您在两个不同的翻译单元中对同一个函数有两个不同的定义,这违反了ODR,这意味着它是未定义的行为。
理论上,结果可以是任何东西。编译错误,崩溃,披萨的无声在线订单,任何。甚至在调试和发布编译中也有不同的行为。
实际上,我猜测发生以下情况:链接调试版本时,链接器会在两个TU中看到两次定义的相同符号,这仅适用于模板和内联函数。由于ODR,它可以假设两个定义都是等价的,并从special.cpp
中选择一个,因此您巧妙地得到了您期望的行为。
在发布版本期间,编译器在编译special::call<string>
期间内联对main.cpp
的调用,因此您获得在该TU中看到的唯一行为:“不那么特别”。
那么你怎么能解决这个问题呢?
为了只为该专业化定义一个定义,你必须像你一样在一个TU中定义,但你必须声明任何一个完全专业化其他TU,意思是声明标题special.h
中存在专门化:
// in special.h
template<>
void special::call(const std::string&);
或者,更常见的是,在标题中定义,因此可以在每个TU中看到它。由于完全专业化的功能模板是普通功能,因此您必须将其内联定义:
// in special.h
template<>
inline void special::call(const std::string&)
{
puts("very special");
}