未定义的模板方法技巧?
我的一位同事告诉我,他在团队中使用的一个小设计让我心潮澎湃。这是一种traits类,他们可以以一种极其解耦的方式专门化。
我很难理解它是如何工作的,而且我仍然不确定我的想法,所以我想我会在这里寻求帮助。
我们在这里谈论 g++,特别是版本 3.4.2 和 4.3.2(似乎两者都适用)。
这个想法很简单:
1- 定义接口
// interface.h
template <class T>
struct Interface
{
void foo(); // the method is not implemented, it could not work if it was
};
//
// I do not think it is necessary
// but they prefer free-standing methods with templates
// because of the automatic argument deduction
//
template <class T>
void foo(Interface<T>& interface) { interface.foo(); }
2- 定义一个类,并在源文件中专门化该类的接口(定义其方法)
// special.h
class Special {};
// special.cpp
#include "interface.h"
#include "special.h"
//
// Note that this specialization is not visible outside of this translation unit
//
template <>
struct Interface<Special>
{
void foo() { std::cout << "Special" << std::endl; }
};
3- 使用起来也很简单:
// main.cpp
#include "interface.h"
class Special; // yes, it only costs a forward declaration
// which helps much in term of dependencies
int main(int argc, char* argv[])
{
Interface<Special> special;
foo(special);
return 0;
};
如果没有翻译单元,它是一个未定义的符号为Special
定义了Interface
的特化。
现在,我本以为这需要 export
关键字,据我所知,该关键字从未在 g++ 中实现过(并且只在 C++ 编译器中实现过一次,其作者建议任何人不要这样做,因为他们花费了时间和精力)。
我怀疑这与链接器解析模板方法有关...
- 您以前遇到过类似的事情吗?
- 它符合标准还是你认为它有效是一个幸运的巧合?
我必须承认我对这个结构感到非常困惑......
A colleague of mine told me about a little piece of design he has used with his team that sent my mind boiling. It's a kind of traits class that they can specialize in an extremely decoupled way.
I've had a hard time understanding how it could possibly work, and I am still unsure of the idea I have, so I thought I would ask for help here.
We are talking g++ here, specifically the versions 3.4.2 and 4.3.2 (it seems to work with both).
The idea is quite simple:
1- Define the interface
// interface.h
template <class T>
struct Interface
{
void foo(); // the method is not implemented, it could not work if it was
};
//
// I do not think it is necessary
// but they prefer free-standing methods with templates
// because of the automatic argument deduction
//
template <class T>
void foo(Interface<T>& interface) { interface.foo(); }
2- Define a class, and in the source file specialize the interface for this class (defining its methods)
// special.h
class Special {};
// special.cpp
#include "interface.h"
#include "special.h"
//
// Note that this specialization is not visible outside of this translation unit
//
template <>
struct Interface<Special>
{
void foo() { std::cout << "Special" << std::endl; }
};
3- To use, it's simple too:
// main.cpp
#include "interface.h"
class Special; // yes, it only costs a forward declaration
// which helps much in term of dependencies
int main(int argc, char* argv[])
{
Interface<Special> special;
foo(special);
return 0;
};
It's an undefined symbol if no translation unit defined a specialization of Interface
for Special
.
Now, I would have thought this would require the export
keyword, which to my knowledge has never been implemented in g++ (and only implemented once in a C++ compiler, with its authors advising anyone not to, given the time and effort it took them).
I suspect it's got something to do with the linker resolving the templates methods...
- Do you have ever met anything like this before ?
- Does it conform to the standard or do you think it's a fortunate coincidence it works ?
I must admit I am quite puzzled by the construct...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
就像@Steward 怀疑的那样,它是无效的。从形式上来说,它有效导致未定义的行为,因为标准规则规定对于违规行为不需要诊断,这意味着实现可以默默地做任何它想做的事情。于
14.7.3/6
实际上,至少在 GCC 上,它会隐式实例化主模板
Interface
,因为专门化未声明且在main
中不可见,然后调用接口::foo
。如果它的定义是可见的,它就会实例化成员函数的主要定义(这就是为什么当它被定义时,它不起作用)。实例化的函数名称符号具有弱链接,因为它们可能在不同的目标文件中多次出现,并且必须在最终程序中合并为一个符号。相反,不再是模板的显式专业化的成员具有强链接,因此它们将支配弱链接符号并使调用最终在专业化中结束。所有这些都是实施细节,标准没有弱/强链接的概念。您必须在创建
special
对象之前声明专业化:标准将其暴露出来(我强调)
Like @Steward suspected, it's not valid. Formally it's effectively causing undefined behavior, because the Standard rules that for a violation no diagnostic is required, which means the implementation can silently do anything it wants. At
14.7.3/6
In practice at least on GCC, it's implicitly instantiating the primary template
Interface<T>
since the specialization wasn't declared and is not visible inmain
, and then callingInterface<T>::foo
. If its definition is visible, it instatiates the primary definition of the member function (which is why when it is defined, it wouldn't work).Instantiated function name symbols have weak linkage because they could possibly be present multiple times in different object files, and have to be merged into one symbol in the final program. Contrary, members of explicit specializations that aren't templates anymore have strong linkage so they will dominate weak linkage symbols and make the call end up in the specialization. All this is implementation detail, and the Standard has no such notion of weak/strong linkage. You have to declare the specialization prior to creating the
special
object:The Standard lays it bare (emphasize by me)
那非常整洁。我不确定它是否保证在任何地方都能工作。看起来他们正在做的是故意未定义模板方法,然后定义隐藏在自己的翻译单元中的专业化。它们依赖于编译器对原始类模板方法和专业化使用相同的名称重整,我认为这可能是非标准的。然后,链接器将查找类模板的方法,但会查找专门化。
但这也存在一些风险。例如,没有人,甚至链接器,会选择该方法的多个实现。模板方法将被标记为 selectany,因为模板意味着内联,因此如果链接器看到多个实例,它不会发出错误,而是选择最方便的一个。
不过,这仍然是一个不错的技巧,尽管不幸的是,它的奏效似乎确实是一个幸运的巧合。
Thats pretty neat. I'm not sure if it is guaranteed to work everywhere though. It looks like what they're doing is having a deliberately undefined template method, and then defining a specialization tucked away in its own translation unit. They're depending on the compiler using the same name mangling for both the original class template method and the specialization, which is the bit I think is probably non-standard. The linker will then look for the method of the class template, but instead find the specialization.
There are a few risks with this though. No one, not even the linker, will pick up multiple implementations of the method for example. The template methods will be marked selectany because template implies inline so if the linker sees multiple instances, instead of issuing an error it will pick whichever one is most convenient.
Still a nice trick though, although unfortunately it does seem to be a lucky coincidence that it works.