☰
VS2010类视图太乱?用__INTELLISENSE__条件编译隐藏MFC重写类
2026/10/17 10:03:12 网站建设 项目流程

如果你用VS2010写过MFC界面,大概率见过这种场面:类视图一打开,左边全是CMyButton、CMyListCtrl、CMyEdit这类重写出来的控件类,真正要找的业务窗体类反而被挤到下面。刚开始我还能忍,等重写控件类一多,每天按Ctrl+Alt+L都要在那棵树上翻半天。更烦的是,这些类还不能不写,MFC要做自绘、拦截消息、扩展功能,几乎绕不开继承重写。于是很多人会问:有没有办法写点代码,让VS2010的类视图别把我自己写的这些类显示出来?

答案是有的,但不像改个配置那样直接,得从VS2010类视图的底层机制入手。这篇文章会先把类视图的符号来源讲清楚,再给出可复现的“代码屏蔽”方案,顺便把我自己踩过的坑、副作用和替代做法都摊开说,适合被类视图搞烦的MFC开发者参考。

1. 先弄清类视图里的“幽灵类”是从哪冒出来的

1.1 VS2010的类视图不再依赖BSC,而是IntelliSense数据库

早期Visual Studio处理C++类视图,靠的是BSC(浏览信息文件)。编译器在编译每个源文件时额外输出符号信息,最后由系统汇总成数据库,类视图从中读取。这套机制的问题是:BSC不会自动更新,代码改完还得手动重编浏览信息,非常不灵活。VS2010把C++的IntelliSense整体重写了,符号数据统一落到项目目录下的.sdf文件里,类视图、代码编辑器、查找引用、自动补全全走这一套数据库。

这一点很重要,因为它决定了“隐藏类”思路的方向。类视图不是靠扫描磁盘文件来列出类的,它靠的是IntelliSense对代码做语法和语义解析后生成的符号表。换句话说,只要让IntelliSense在解析阶段“看不到”某个类定义,这个类就不会出现在类视图里,哪怕编译器编译时完全正常。

1.2 MFC重写类为什么特别容易“塞满”类视图

MFC的重写类有两个特点。一是定义里全是宏,常见的有DECLARE_DYNAMIC、DECLARE_MESSAGE_MAP、afx_msg这些。VS2010的IntelliSense引擎面对这类宏时,解析速度会下降,偶尔还会在类视图里生成一些你根本不想看的中转节点或多余成员。二是用户自己派生出来的控件类往往数量很多:按钮要重写一个、列表视图要重写一个、编辑框还要重写一个,一个界面模块随手就是五六个。

更让类视图混乱的是,MFC类向导会同时生成头文件和实现文件,类视图会在两者之间跳来跳去。如果你在某个窗口里只用了这些重写类的实例,类视图里照样把它们全部摊开。时间一长,类视图就变成了重写类的“仓库”,真正关心的高层业务类反而被埋住。于是大家开始思考能不能用代码屏蔽,而不是靠眼睛硬找。

1.3 突破口:IntelliSense和编译器对代码的看法不一样

常规的#ifdef条件编译只会让编译器听你的,但VS2010的IntelliSense解析代码时有自己的一套规则。关键点:IntelliSense在扫描代码时会额外预定义一个宏,叫__INTELLISENSE__,而真正的编译器cl.exe不会定义它。

这就给了我们一个完全合法的错位空间:

#if !defined(__INTELLISENSE__) // 这段代码只有编译器看得到,IntelliSense会直接跳过 class CMyButton : public CButton { // ... }; #endif

IntelliSense解析时定义了__INTELLISENSE__,所以#if条件不成立,类定义对它来说不存在;编译器编译时没定义这个宏,条件成立,类正常编译。类视图的数据来自IntelliSense,于是这个类就从类视图里消失,但程序还是能正常编译运行。这就是“代码屏蔽”的核心原理,听起来很取巧,但确实行得通。

2. 用INTELLISENSE条件编译把指定类藏起来

2.1 最小可行示例:隐藏一个CButton派生类

下面是一个典型的MFC按钮重写类,正常情况下它会出现在类视图里:

// MyButton.h #pragma once #include <afxwin.h> class CMyButton : public CButton { public: CMyButton(); virtual ~CMyButton(); protected: virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); DECLARE_MESSAGE_MAP() };

注意MFC的类一般还会带DECLARE_DYNAMIC之类的宏,我这里为了展示核心逻辑做了精简,你实际项目里的头文件比这个长一些,但不影响思路。把它藏起来只需要在class前后各加一行:

// MyButton.h #pragma once #include <afxwin.h> #if !defined(__INTELLISENSE__) class CMyButton : public CButton { public: CMyButton(); virtual ~CMyButton(); protected: virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); DECLARE_MESSAGE_MAP() }; #endif

把文件存盘,等IntelliSense重新解析完成后,Ctrl+Alt+L打开类视图看一下,CMyButton已经不在列表里了。但你去编译项目,编译器照样认识这个类,对话框里如果创建了CMyButton的实例,编译链接一切正常。

2.2 三种屏蔽粒度:单类、整段代码、整个头文件

第一种粒度是只屏蔽单个类,就像上面那样,适合隐藏某个内部控件子类。

第二种粒度是想屏蔽一段一起出现的类,可以把它们全部放进同一个条件编译块里。比如一个“内部UI控件”头文件里同时有三个自绘控件类,那就整体包起来:

#if !defined(__INTELLISENSE__) class CMyButton : public CButton { /* ... */ }; class CMyEdit : public CEdit { /* ... */ }; class CMyListCtrl : public CListCtrl { /* ... */ }; #endif

第三种粒度是把整个头文件的内容都包住。这种情况通常出现在你自己写的基础组件层,整个头文件都不想被类视图索引。但这里有个大坑:如果其他业务类通过#include引用这个头文件,IntelliSense同样会去解析包含链,整个头文件不见得能完全躲开。而且一旦整个文件消失,编辑器里所有引用这些类的地方都会跟着报错,代价很大。

我个人比较推荐的是第二种加一点变体:只屏蔽真实定义,但给IntelliSense留一个前置声明。这样至少类名还在,编辑器不会把标识符当成完全不存在:

#if defined(__INTELLISENSE__) class CMyButton; #else class CMyButton : public CButton { // 完整的真实定义 }; #endif

这个做法的限制也很明显:前置声明属于不完整类型,代码里只能使用指针或引用,不能定义对象,也不能访问成员函数。如果业务代码里到处是CMyButton btn;这种写法,那还是会飘红。

2.3 怎么验证藏成功了:三端交叉检查

改完代码别急着下结论,我一般会做三个验证:

第一,类视图。按Ctrl+Alt+L打开类视图,等两秒刷新,看类是不是真没了。这里要注意,IntelliSense解析是异步的,文件保存后要等后台线程跑完,所以类视图不会“秒变”。万一等半天还没变,多半是.sdf缓存的问题,后面会说。

第二,编译。直接Ctrl+Shift+B编译项目,确认没有“未定义的标识符”之类的错误。这个验证是必须的,因为条件编译写错了,编译器是真会出错的,不会因为你只想藏类就放过你。

第三,编辑器。随便打开一个引用了这个类的cpp文件,看看有没有红色波浪线。如果在IntelliSense眼里这个类真的被藏了,引用处大概率会飘红。这个现象不是bug,而是副作用,必须提前知道。

3. 藏起来之后会有什么“副作用”,以及如何缓解

3.1 最先碰到的是满屏红波浪线

不少人在把类藏起来之后,第一反应是“怎么到处都是红色波浪线”。原因很简单:IntelliSense解析源文件时,会把include进来的头文件也一起看一遍,而你在头文件里用__INTELLISENSE__把类定义屏蔽了,所以它对整个代码库而言“看不到这个类型”。只要哪里有CMyButton这个标识符,哪里就会报错。

这就形成了一个两难:类视图是干净了,但编辑器里的错误提示会让人直接怀疑人生。所以并不是所有类都适合用这种方式隐藏。建议只对“你不再需要手动编辑”的底层封装类使用这种屏蔽,如果是项目中还要频繁调用、修改的类,那代价太大。

3.2 自动补全、转定义、查找引用全部失效

这点比红波浪线更隐蔽。类视图和IntelliSense共用一套符号数据库,既然类定义被IntelliSense跳过了,那么编辑器对它的自动补全自然也没有。你输入CMyButton想看看它有哪些成员,列表是空的;右键“转到定义”,会告诉你找不到符号;查找所有引用也基本不会有结果。

对于业务代码来说,这几乎是不可接受的。本来MFC写起来就繁琐,再用这种屏蔽把IDE能力砍一刀,效率反而更低。所以我在实际项目里通常会让屏蔽范围尽量小,只针对那种“内部实现类、日常不会手工触碰”的类型。

3.3 其他环境不一致:命令行编译和第三方插件

还要考虑环境问题。__INTELLISENSE__由VS的IntelliSense引擎定义,命令行编译、脚本编译都不会定义这个宏,所以用脚本编译项目时这些类都会正常参与编译,不会出错。但反过来,如果你的同事装了一些增强工具,比如某些C++辅助插件,它们不一定完全遵守VS的IntelliSense宏约定,可能出现“别人机器上照样显示类”的情况。这个问题不常见,但团队协作时要有个心理预期。

另外,VS2010里的对话框资源编辑器也会受影响。在资源编辑器里给某个控件关联变量时,如果指定的类型是被屏蔽掉的类,编辑器无法正确识别,轻则无法补全,重则弹出“未知类型”。这个副作用我实际遇到过,后面会详细说。

3.4 缓解手段:把“对外可见接口”和“内部实现”分离

如果确实想让类视图干净,又不想影响日常工作,我推荐一个工程做法:把对外接口和内部实现分离。外部业务代码只跟一个稳定接口类打交道,内部那些重写MFC控件的类全部藏在实现层。比如你给按钮设计一个自绘外观,对外暴露的只是一个带方法的封装类,真正的CMyButton只出现在封装类的.cpp里,不在头文件里露脸。

这样即便不用__INTELLISENSE__,类视图里也不会混入太多内部控件类。MFC虽然不像现代C++那样鼓励接口分离,但用这个思路整理界面代码完全是可行的。代码结构一旦清晰,类视图那点杂乱完全在可接受范围内。

4. 如果不想动代码:项目层面和操作层面的几个替代做法

__INTELLISENSE__方案好不好?好,但它伤敌一千自损八百,很多人接受不了编辑器彻底失去类型的代价。那还有没有不动代码或者少动代码的办法?有几个,但都各有局限。

4.1 关闭IntelliSense,退回老式浏览信息

VS2010的项目属性里有一个“启用IntelliSense”相关选项,位置大致在“配置属性 -> C/C++ -> 浏览信息”。你把它关掉后,项目的.sdf数据库就不会再维护,类视图会退回依赖BSC。这样做的确能摆脱IntelliSense对类视图的控制,但也意味着自动补全、语法错误提示这些能力全部消失。对今天的开发习惯来说,这个方案基本等于自残。我不推荐,除非项目本身小到根本不需要智能提示。

4.2 调整.vcxproj中文件的Item Type

用文本编辑器打开.vcxproj,能看到一组这样的节点:

<ItemGroup> <ClInclude Include="MyButton.h" /> </ItemGroup>

ClInclude表示这个文件会被当作C++头文件参与工程管理。如果把它改成None:

<ItemGroup> <None Include="MyButton.h" /> </ItemGroup>

VS在项目层面的扫描逻辑里就不会把它当作一个独立的待索引头文件。但要注意,这个做法并不能保证隐藏。只要某个.cpp文件里写了#include "MyButton.h",IntelliSense在解析.cpp时照样会顺着包含链看到类,类视图里类还是有可能会冒出来。所以它只能算一种“降低索引优先级”的偏方,不能作为稳定方案。

4.3 用类视图的排序、搜索和命名规范来“管理混乱”

VS2010类视图有搜索框,工具栏上也有一些排序方式可以切换。与其想方设法把某些类抹掉,不如给内部重写类一个统一的命名前缀,比如UiCtrl、Internal。业务类一眼望去就能和内部类区分开。实际上在真实项目里,类视图乱不乱,很大程度取决于命名和组织习惯,而不是VS本身的表现力。我把一套内部控件类的头文件集中放到ControlEx目录后,类视图里虽然还是一大坨,但起码结构是能看懂的。

4.4 团队协作时的“别扭”要提前说

如果你的代码要提交到共享仓库,别人拉下来后也会面对这些屏蔽。人家可能不知道这段代码的含义,看到编辑器一片红会直接跑来问你。如果非要屏蔽,一定在头文件顶部留下注释,说明“此类被IntelliSense有意屏蔽,原因和解除方法写在这里”。这不是可有可无,而是维护基本代码卫生的问题。

5. 实战踩坑记录:我屏蔽一个按钮类后遇到的问题

讲几个我实际掉进去过的坑,这些坑比理论更能说明问题。

5.1 类视图没立刻消失,一直以为屏蔽失败

有一次我改完代码,类视图里类还在,我以为条件编译写错了,反复检查了好几遍。后来发现是IntelliSense还在后台跑,或者.sdf文件已经有点损坏了。处理办法很简单:把项目的.sdf文件删掉,关闭VS再重新打开,让它重新生成数据库。这种做法对有问题的类视图特别有效,缺点是首次重建会卡一会儿,项目大的时候可能要等十几秒。

5.2 条件编译方向写反,真实类被编译器扔掉了

我后来换了一个类试隐藏,想用#if defined(__INTELLISENSE__)的写法,结果逻辑写反,绕来绕去把真实定义给包住了。一编译,整个工程报一堆“CMyButton未定义”。那种报错会让你瞬间明白:IntelliSense被瞒住顶多是编辑器红,编译器被瞒住就是构建直接挂。这里给个硬经验:条件编译块前后一定要加注释,标明“这一段是真实代码,只给编译器看”和“这一段是智能感知专用,不要编译”。

5.3 对话框资源编辑器关联变量时的连带影响

MFC开发经常要在资源编辑器里右键控件添加变量。当我把某些控件子类通过__INTELLISENSE__屏蔽后,再去给控件关联变量,弹出的变量类型下拉框中找不到这些类,手动输入也会被编辑器标成“未知类型”。为了解决这个问题,我把变量的类型改成泛化的CWnd*,运行时再GetDlgItem转换。能绕过去,但代码显然没以前直接了。

5.4 教训总结:什么情况下值得屏蔽,什么情况下不值得

我个人最后的结论是:不要为了“类视图干净”把所有MFC重写类都屏蔽。类视图本质上是一个开发工具,它乱一点不影响编译和运行,真正影响的是你找类的效率。你能通过命名规范、目录组织解决的问题,尽量别用条件编译去解决。个别特别干扰视线的内部辅助类,用__INTELLISENSE__精准屏蔽一下没问题,但一定要接受编辑器自动补全、查找引用部分失效的代价。

如果哪天你发现类视图彻底卡死或显示错乱,先别急着屏蔽这个屏蔽那个,赶紧删.sdf重建数据库,比做任何代码改造都快。等到项目稳定了,再回头琢磨这些类到底要不要从类视图里藏起来,心里就踏实多了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询