TestQueue_16th_2.obj : error LNK2019: 无法解析的外部符号 "class std::basic_ostream
> & __cdecl operator<<
该符号在函数 _wmain 中被引用
首先一般按照平常的头文件和源文件分开处理使用包含形式编译,但是实际上我们包含的是头文件而不是源文件。注定是个错误。
先解析一下包含编译和分离编译:
包含模式:#include A 就是试用包含模式,当包含的是A.h文件,则需要在本文件的obj对象中为A.Obj增加地址映射;若是A.cpp或A.hpp,则是直接分别映射A.obj的每个函数。
分离模式:首先得支持export关键字的操作定义,一般直接在头文件中定义改模板为export就行了,简单,具体操作交给编译器;现实是,我没用过这类似的编译模式,就是在YY。
在VS2012中编译下面代码:
template
export class QueueItem
结果
1>d:\cpp_lab\testqueue_16th\testqueue_16th\queue.h(8): warning C4237: 目前还不支持“export”关键字,但已保留该关键字供将来使用
简单点:VS不支持分离式编译模式,至于Linux,我好久没见过啦。MAC的Xcode还没有时间去验证。暂时搁置。
下面是一段转载翻译形式分享:
来自《C++ Template: The Complete Guide》
http://wenku.baidu.com/view/68e2623a43323968011c9225.html
主要是有关编译器和模板编译的内容。
首先,C++标准中提到,一个编译单元[translation unit]是指一个.cpp文件以及它所include的所有.h文件,.h文件里的代码将会被扩展到包含它的.cpp文件里,然后编译器编译该.cpp文件为一个.obj文件,后者拥有PE[Portable Executable,即windows可执行文件]文件格式,并且本身包含的就已经是二进制码,但是,不一定能够执行,因为并不保证其中一定有main函数。当编译器将一个工程里的所有.cpp文件以分离的方式编译完毕后,再由连接器(linker)进行连接成为一个.exe文件。
包含编译模式会把所有的的数据成员添加在本文件.obj中,
而".h"没有生成obj对象,而是hpp和cpp生成obj对象,然后编译器,通过头文件的映射关系将函数链接起来,例如:
//---------------test.h-------------------//
void f();//这里声明一个函数f
//---------------test.cpp--------------//
#include”test.h”
void f()
{
…//do something
} //这里实现出test.h中声明的f函数
//---------------main.cpp--------------//
#include”test.h”
int main()
{
f(); //调用f,f具有外部连接类型
}
当编译器在main.obj中 call f 时,链接器从头文件表中寻找f的实现代码,然后将test.obj的实际地址和对象链接在代码段中成为exe。
但是我开始还是忘记了模板未特化之前只是一段数据声明,实例化之后才是代码(具体模板代码存放在代码段,在特化之前没有具体含义)。所以按照编译结果链接起来,发现test.obj的该代码段是执行的是不匹配的。这也是模板编译目前在VS只支持包含模式的原因,必须将定义和声明放在相同的源文件中,这样子在编译期main.obj call f(如文库的文档所言,应该jmp 0X??? )指向的地址才是特化后的代码地址。最后才能转化为机器码。
包含模式意味编译器多次实例化了同一个模板的各种版本(含重复部分),实际使用的话,我们可以再头文件中显示实例化某个常有版本,然后使用extern 关键字拓展某个版本的声明,当然这样子可能会导致代码段冗余。
代码如下:
template
class QueueItem
{
private:
friend class Queue
; template
friend std::ostream & operator << (std::ostream & os,const Queue
&q); QueueItem(const Type &element):item(element),next(nullptr) {}; Type item; QueueItem * next; };
友元模板的声明在此例子中其实没有什么好说的,本身type的类型就不会实例化出其他版本的operator<<,所以
template
friend std::ostream & operator << (std::ostream & os,const Queue
&q);
改成上面的形式也不会有什么负面影响,当还是要坚持版本1,尽量在代码在自己的思维控制之下,能自己解决的问题不交给其他代码库或者
template
void Queue
::copy_elems(const Queue
&Q) { std::for_each( Q.m_pHead,Q.m_pTail, [=](QueueItem
i) { this->push_back(i.item); } ); } template
template
void Queue
::copy_elems(Iter begin,Iter end) { std::for_each( begin, end, [this](decltype(*begin) i) { this->push_back(i); } ); /*for (; begin != end; ++begin) { this->push_back(*begin); }*/ } template
std::ostream & operator<< (std::ostream & os,const Queue
&q) { os<<"<"; QueueItem
*p; for (p = q.m_pHead; p!=nullptr ; p = p->next) { os<
item<<" "; } os<<">"; return os; }
使用了decltype来获取参数中的实际类型来声明i,之前说过auto/decltype这把双刃剑的使用范围是在接口交互,今天把它贴出来做个印证。
其实,还有一组代码是这段博客的要讲的:
std::for_each(
begin,
end,
[this](typename Iter::value_type i)
{
this->push_back(i);
}
);
照样编译通过并执行通过,模板推导竟然可以将类型上升命名空间对象。而我用Iter::value_type iterator等方式声明参数时,确实改参数无法实例化。参数就是一个vector
::iterator(begin和end)。
具体代码见链接:
例子源码下载