在我正在研究的项目的一些序列化代码中,我有一个类型,其大小取决于编译器。为了解决这个问题,我决定使用模板专业化,这非常有效。一切都在编译时解决。代码看起来有点像这样(不是真正的代码,只是一个例子):
template <int size>
void
special_function()
{
std::cout << "Called without specialization: " << size << std::endl;
}
template <>
void
special_function<4>()
{
std::cout << "dword" << std::endl;
}
template <>
void
special_function<8>()
{
std::cout << "qword" << std::endl;
}
int
main()
{
special_function<sizeof(int)>();
return 0;
}
在我的32位系统上,按预期执行上述程序输出dword
。但是这样做而不只是做if (sizeof(int) == 4) { ... } else if ...
的全部意义在于我曾希望编译器只生成适当函数的代码。由于special_function<4>
是此程序中唯一调用的,我希望它是编译器生成的唯一一个(在本例中为gcc 4.1.2,在x86 Linux上)。
但这不是观察到的行为。
虽然它确实有效,但每个模板特化的代码都会生成,尽管从未使用过。但是,不生成通用定义。
我应该提到这是一步编译,而不是编译到中间对象文件后跟一个链接。在这种情况下,将死代码删除推迟到链接阶段似乎很自然,我知道链接器并不总是非常擅长这一点。
有谁知道发生了什么事?我在这里缺少模板专业化的微妙之处吗?上帝知道魔鬼在C ++的细节中。
编辑:因为已经提到过,所以-O3和-Os都会出现这种情况。
EDIT2:Rob建议将函数放在匿名命名空间中。这样做并使用任何级别的优化进行编译确实会删除死代码,这很好。但我很好奇,所以我尝试用以下程序做同样的事情:
namespace {
void foo() { std::cout << "Foo!" << std::endl; }
void bar() { std::cout << "Bar!" << std::endl; }
}
int
main()
{
foo();
return 0;
}
这里的想法是看看Rob的解决方案是否实际上与模板特化相关。事实证明,上面编译时启用了优化的代码忽略了可执行文件中bar()
的未使用定义。所以似乎虽然他的答案解决了我的直接问题,但它并没有解释为什么根本没有编译未使用的模板特化。
是否有人知道该标准的相关片段可以解释这一点?我一直以为模板只是在使用时生成,但对于完全专业化可能不是这样......
答案 0 :(得分:8)
示例中的模板特化是具有外部链接的函数。编译器无法知道不会从另一个翻译单元调用它们。
在我的g ++ 4.7.2 Ubuntu系统上,将模板放入匿名命名空间并使用-O3
进行编译会阻止生成未使用的函数。
同样,声明函数模板static
会产生预期的效果。
答案 1 :(得分:3)
这是一个特殊的问题。我稍微研究了一下,这个问题与模板专业化无关。我猜g ++默认情况下不会删除未使用的符号。如果您以后想要将输出链接到另一个程序,这是有道理的。
但是,您可以使用命令行选项来删除未使用的符号。有关详细信息,请参阅此帖:
How to remove unused C/C++ symbols with GCC and ld?
但也可以看到
Using GCC to find unreachable functions ("dead code")
在这里
Dead code detection in legacy C/C++ project
为了试试这个,我修改了代码如下:
#include <iostream>
void junk_function() {
std::cout<<"test" << std::endl;
}
template <int size>
void special_function()
{
std::cout << "Called without specialization: " << size << std::endl;
}
template <>
void special_function<4>()
{
std::cout << "dword" << std::endl;
}
template <>
void special_function<8>()
{
std::cout << "qword" << std::endl;
}
int main()
{
special_function<sizeof(int)>();
return 0;
}
然后将此代码存储到sp.cpp。首先,
g++ -Os sp.cpp -o sp
nm sp
并得到了这个(注意,为了便于阅读,我删除了一堆符号):
0804879a T _Z13junk_functionv
080487b8 T _Z16special_functionILi4EEvv
080487f5 T _Z16special_functionILi8EEvv
似乎有两个未使用的符号。我也尝试过-O1,-O2,-O3,并且也一样。
下一步:
g++ -Os -fdata-sections -ffunction-sections sp.cpp -o sp -Wl,--gc-sections
nm sp
得到了这个:
0804875a T _Z16special_functionILi4EEvv
就是这样。所以看起来你只需要传递正确的参数来告诉g ++去除未使用的符号。在Mac上,我猜他们有-dead_strip选项,但我不知道为什么它在g ++中不起作用(尽管在手册页中提到过。不可否认,我没有深入研究这个,所以有可能是一个我错过的精美印刷品。
我认为链接时默认情况下Visual C ++的链接器会剥离,但我没有测试。也许别人可以插话。