SmartPtr
本博客已搬至:http://www.cnblogs.com/baiyanhuang/
posts - 29,comments - 176,trackbacks - 0
By SmartPtr(http://www.cppblog.com/SmartPtr/)

这几天工作时碰到一个C++的编译错误(我使用的是Visual C++ 7.0),说是有一个类重复定义,仔细想想我们的这个项目也是做了好几个Release了, 内部代码应该不会有这样的低级错误, 真把类型给重复定义了,检查结果正如我预料的一样。 就这样, 我左右没找到原因,被一个编译错误给卡在那里了。(在我的概念中, 程序错误的等级为:编译错误->链接错误->逻辑错误, 此错误属于最低级 )。这时我仔细看了一下错误提示, 发现重复定义是由于从两个不同的路径包含了同一个头文件而引起的,同事也建议从另外一个路径打开工程试试, 这才慢慢发现了原因。这个原因可能有些拗口,而事实上要出现这种错误也有些曲折 让我从不同情况下的类型重定义来解释一下吧。

我总结的C++中类型重定义情况有三。

1 没有在文件头加#pragma once指示符。

Type1.h:  #pragma once的作用是保证本文件只被编译一次,如果没有在Type1.h中加这句话那么在main.cpp里面包含了两次Type1.h 就相当于在main.cpp里面定义了两次Type类, 自然就是类型重定义了。

//#pragma once
class Type
{  

};

Main.cpp:

#include "Type1.h"
#include 
"Type1.h"
int main(int argc, char *argv[])
{
   
return 1;
}

 

2 两个不同的头文件中定义了相同的类型(均有#pragma once

Type1.h:Type2.h:Main.cpp:

#pragma once
class Type
{

};

 

#pragma once
class Type
{     

};

 

  这里main.cpp中同时包含了Type1.h, Type2.h两个头文件, 虽然其文件头都有#pragma once,但因为是不同的文件, 预编译器还是会两次把Type类的定义放在Main.cpp中, 所以也会出现了重定义。

#include "Type1.h"
#include 
"Type2.h"
int main(int argc, char *argv[])
{
   
return 1;
}

 

3 从两个不同的路径包含了同一个头文件

  前面两种是比较常见, 也是比较容易解决的情况, 而这里要讲的第三种情况, 比较少见, 而且一般出现在有虚拟映射盘的时候。(这样才能做到从两个不同的路径包含同一个头文件), 其他会在什么时候出现, 我还没想到, 知道的朋友顶一下:)。下面我来分析一下:
1 VC工程在D:\Test目录下。
2 映射虚拟盘XD:\Test.
不熟悉的网友可以按此操作: 开始->运行->在运行窗口输入:cmd->cmd窗口输入:
Subst X: D:\Test->回车。
3 该工程有文件Type1.h, main.cpp

Type1.h:

#pragma once
class Type
{

};

Main.cpp:

main.cpp这样包含了两个头文件, 从本质上来讲, 它们都对应于物理盘D:\Test下的文件Type1.h, 是同一个文件。但在不同的操作下, VC对其有不同的解释。#include "X:\Type1.h"用的是绝对路径, 自然没有什么异议, #include "Type1.h"却有些变化:
*假如我从D:\Test\下打开工程, 那么#include "Type1.h"其实就是#include "D:\Test\Type1.h"
*假如从X:\下打开工程,那么#include "Type1.h"就解释为#include "X:\Type1.h"

#include "Type1.h"
#include 
"X:Type1.h"
int main(int argc, char *argv[])
{
   
return 1
;
}

    这里我们在

4 D:\Test下打开工程, 编译, 出现类型Type重复定义错误

这种情况下,main.cpp预编译为:

Main.cpp:

只保证本文件被编译一次, 这里VC将其认为是两个不同的文件, 所以都要编译, 出现编译错误自然也就不奇怪了。
   
当然, 这里如果从X:\ 下打开工程的话,VC就会认为都是从X:\Type1.h下包含这个文件,#pragma once起到了作用, 也就不会出现类型重定义了

#include "D:TestType1.h"
#include 
"X:Type1.h"
int main(int argc, char *argv[])
{
   
return 1;
}

  #pragma once

总结

我在VC7, VC8,Dev C++中都测试了第三种情况, 发现只有Dev C++是可以通过编译的。这可能是微软VC#pragma once还不够智能吧,轻易的被Windows的虚拟盘给蒙蔽了双眼, 看不到其本质(只是猜测, 或许VC这么处理是有其他用意的)。

因为在稍大一点的工程开发中, 我们一般都会用虚拟盘来方便工作, 一是访问快捷,简化了路径, 二是因为多人协同开发,我们一般希望大家源代码路径相同,但我们不应强制要求大家都把源代码放死在某一目录下, 这时把你放源代码的路径映射为一个虚拟盘(比如说统一为X:)就能把大家的代码路径统一起来了。但是另一方面,有了虚拟盘, 就为出现类型重定义提供了条件, 以下是我得出的两个解决方法:

1
抛弃#pragma once使用古老但集稳定性与移植性于一身的

来保证头文件只被编译一次。这样不管是包含两个相同的文件,还是包含两个不同的文件,或是包两个文件相同但路径不同的文件, 只要_XXX_H被定义过, 就不会再编译那个编译(但这里我们要保证_XXX_H的唯一性, 如果两个不同的头文件里用了同一_XXX_H,是会出问题的)

#ifndef _XXX_H
#define _XXX_H
...

#endif

 

2 在包含头文件时,不要使用绝对路径, 哪怕那是虚拟盘的绝对路径。

posted on 2007-07-05 17:49 SmartPtr 阅读(5785) 评论(5)  编辑 收藏 引用

FeedBack:
# re: C++中的类型重定义
2007-07-05 23:08 | cpper
学到了!!!!!  回复  更多评论
  
# re: C++中的类型重定义
2007-07-06 01:52 | 至尊拖鞋
见识了,还没用过虚拟盘映射功能的  回复  更多评论
  
# re: C++中的类型重定义
2007-07-06 14:32 | william
也不一定用subst来做虚拟盘,如果是ntfs格式的话 其实做一个虚拟目录比较好  回复  更多评论
  
# re: C++中的类型重定义
2007-07-08 01:32 | 天津大学计算机学院 常兴龙
总结得很不错,在Symbian开发时会经常用的虚拟路径。  回复  更多评论
  
# re: C++中的类型重定义
2007-07-12 15:35 | Rick

VC是用MS自己的Compiler,估计这个Compiler是采用头文件的绝对路径来作为唯一标识的,所以对于同一个文件通过不同的方式(本地和虚拟盘)来include,VC视作为两个不同的头文件。
而DevC++采用GCC的Compiler,这个Compiler发现两个同名的头文件(不管绝对路径和这两个文件是否其实是一个文件)被include后,会检查这两个头文件的修改时间是否一致。如果一致,则认为这两个头文件是一样的,并且头文件包含“#pragma once”的话就只解析一次,不会报错。否则,两个文件修改时间不一样,那会认为是两个不同的文件(不管这两个文件的内容是否一样),所以解析的时候会报重复定义错误。
  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理