为什么我的专用模板函数仅在调试版本中调用?

时间:2013-05-06 06:07:23

标签: c++ xcode

我在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的专门实现被忽略了。

为什么结果不一致?我该怎么做才能确保在发布版本中调用专用函数?

2 个答案:

答案 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"); 
}