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

C++ 为什么我的专用模板函数只在调试版本中调用?,c++,xcode,C++,Xcode,我在我的C++11Xcode项目中使用了模板函数,其中一些函数有专门化。然而,我发现专门化只在调试构建中被调用;如果我在版本中构建,它们将被忽略 我成功地创建了一个非常简单的示例: 特殊.h #include <cstdio> struct special { template<typename T> void call(const T&) { puts("not so special"); } }; #包括 特殊结构 { 模板 无效调用(co

我在我的C++11Xcode项目中使用了模板函数,其中一些函数有专门化。然而,我发现专门化只在调试构建中被调用;如果我在版本中构建,它们将被忽略

我成功地创建了一个非常简单的示例:

特殊.h

#include <cstdio>

struct special
{
    template<typename T>
    void call(const T&) { puts("not so special"); }
};
#包括
特殊结构
{
模板
无效调用(const T&{puts(“不太特别”);}
};
special.cpp

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

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

int main()
{
    std::string str = "hello world";
    special s;
    s.call(123);
    s.call(str);
}
#包括“special.h”
#包括
模板
void special::call(const std::string&){put(“非常特殊”);}
main.cpp

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

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

int main()
{
    std::string str = "hello world";
    special s;
    s.call(123);
    s.call(str);
}
#包括“special.h”
#包括
int main()
{
std::string str=“hello world”;
特别报告员;
s、 电话(123);
s、 呼叫(str);
}
(至少在2013年夏天之前)如果你不想自己创造这个问题,就复制这个问题。首先使用调试配置运行项目,然后在发行版中再次运行它。我期望的结果是:

没有那么特别
非常特别

这确实是我在调试构建配置中得到的。然而,通过发布,我得到了以下信息:

没有那么特别
没那么特别

这意味着special.cpp中
special::call
的专门实现被忽略


为什么结果不一致?我应该如何确保在发布版本中调用专用函数?

您的程序具有UB。明确的专业化或至少其声明必须在使用前可见。[温度说明规范]§6:

如果模板、成员模板或类模板的成员 明确专业化,则应声明该专业化 在第一次使用会导致 在中的每个翻译单元中进行隐式实例化 发生这种使用的原因;无需诊断

将此声明添加到
special.h

template<>
void special::call(const std::string&);
// in special.h
template<>
void special::call(const std::string&);
模板
void special::call(const std::string&);
或者,您可以将专业化本身放入标题中。但是,由于专业化不再是模板,它遵循正常的功能规则,如果放在标题中,则必须标记为
inline


另外,要注意函数模板专门化具有相当特定的行为,通常使用重载比专门化更好。有关详细信息,请参阅。

您违反了一个定义规则(ODR)。那么到底发生了什么?在
main.cpp
中,没有已知的
special::call的专门化。因此,编译器将模板的实例化生成到输出“不太特殊”的翻译单元(TU)中。在
special.cpp
中声明并定义了一个完整的专门化,因此编译器将该定义放入另一个翻译单元。所以在两个不同的转换单元中对同一个函数有两个不同的定义,这违反了ODR,这意味着它是未定义的行为

理论上,结果可以是任何东西。一个编译器错误,一个崩溃,一个无声的比萨饼在线订单,任何事情。甚至调试和发布编译中的不同行为

在实践中,我猜想会发生以下情况:当链接调试构建时,链接器会看到在两个tu中定义了两次相同的符号,这只允许用于模板和内联函数。由于ODR,它可能会假定两个定义是等效的,并从
special.cpp
中选取一个,因此您碰巧得到了预期的行为。
在发布版本构建期间,编译器在编译
main.cpp
期间将调用内联到
special::call
,因此您可以看到该TU中唯一的行为:“不那么特别”

那么你如何解决这个问题呢?
为了对该专门化只有一个定义,您必须像以前一样在一个TU中定义它,但您必须声明在任何其他TU中都有一个完整的专门化,这意味着声明该专门化存在于标题
special.h

template<>
void special::call(const std::string&);
// in special.h
template<>
void special::call(const std::string&);
//在special.h中
模板
void special::call(const std::string&);
或者,更常见的是,在标题中定义它,因此在每个TU中都可以看到。由于完全专用的函数模板是普通函数,因此您必须内联定义它:

// in special.h
template<>
inline void special::call(const std::string&)
{ 
  puts("very special"); 
}
//在special.h中
模板
内联void special::call(const std::string&)
{ 
出售(“非常特别”);
}

将其放入.h而不是.cpp?两种情况下都链接了special.cpp?是的,在两种情况下都编译并链接了special.cpp。如果我将专门化放在头文件中,如果该文件仅包含在一个.cpp文件中,它就会起作用,但否则我会得到一个重复的符号错误。+1是对正在发生的事情的完整解释,而不仅仅是所需的更正。至于从
specials.cpp
中选择函数,我猜想这是由于
.o
文件传递到链接器的顺序造成的;具体地说,我怀疑
specials.o
是在
main.o
之前传递的。我想这就是特定于实现的部分。它将取决于链接器处理对象文件的顺序,而这又取决于通过命令行将对象文件传递给链接器的顺序。这可能取决于链接器如何存储和查找在两个tu中找到的符号。这可能是任何东西,但我猜最明显的是传递的第一个对象文件是第一次处理的,而第一次找到符号是唯一一个被存储和调用的。哦,你是对的,它看起来非常特定于实现,但由于显然行为是稳定的,可能有一个解释。