7

I have templated functions in my C++11 Xcode project and some of them have specializations. However, I have discovered that the specializations only get called in debug builds; if I build in release, they are ignored.

I have successfully created a very simple example:

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 (until somewhere this summer of 2013 at least) to reproduce the issue if you don't want to create it yourself. First run the project with the debug configuration, then run it again in release. The output that I expect is:

not so special
very special

And this is indeed what I get with the Debug build configuration. However, with Release, I get this:

not so special
not so special

Which means the specialized implementation of special::call in special.cpp was ignored.

Why is the result inconsistent? What should I do to ensure that the specialized function is called in release builds?

4

2 回答 2

11

你的程序有UB。显式特化或至少其声明在使用之前必须是可见的。[temp.expl.spec]§6:

如果模板、成员模板或类模板的成员是显式特化的,则应在第一次使用该特化之前声明该特化,这将导致发生隐式实例化,在发生这种使用的每个翻译单元中; 不需要诊断。

将此声明添加到special.h

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

或者,您可以将专业知识本身放入标题中。但是,由于特化不再是模板,它遵循正常的功能规则,inline如果放在标题中,则必须进行标记。

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

于 2013-05-06T06:38:41.463 回答
8

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

理论上,结果可以是任何东西。编译器错误、崩溃、无声在线订购披萨,等等。甚至调试和发布编译中的不同行为。

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

那么你怎么能解决这个问题呢?
为了对该专业化只有一个定义,您必须像以前一样在一个 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"); 
}
于 2013-05-06T06:44:56.037 回答