挪用虚拟函数、一连化视图状态及POD范例观念
在 C++ 中,无法从某个类的结构函数中挪用派生的虚拟函数,因为虚表还没有完全成立。可是在C#中仿佛就可以,是这样吗?为什么会有这种不同呢?
确实如此,在这个方面 C# 与 C++ 是有不同的。在 C++ 中,假如你从结构函数可能析构函数中挪用虚拟函数,编译器挪用的虚拟函数是界说在这个正在被结构的类实例中的(譬喻,假如从 Base::Base 中挪用 Base::SomeVirtFn ),不是最底层派生的实例(the most derived instance),正像你说的那样,因为在最底层派生的结构函数执行之前,虚表还没有完全被初始化。另一种说法是派生类还没有被建设。
Figure 2 虚拟函数 TestSimilarly
当你从析构函数中挪用虚函数时,C++ 挪用该基类的析构函数,因为派生类已经被销毁(其析构已经被挪用)。固然这个行为可导致异常功效(此即为什么从结构函数或析构函数中挪用虚函数被认为是糟糕的编程实践的原因),它是大大都 C++ 措施员必需了然于心的根基知识。
正如你所指出的那样,在 C# 有所差异。托管工具——无论是在 C#,托管 C++ 中,照旧任何其它的 .NET 兼容语言中——是作为其最终范例被建设的,也就是说,假如你从结构函数或析构函数中挪用虚函数,系统挪用的是最末层派生的函数。Figure 1 所示措施举例说明白这一点。假如你编译并运行这个措施,你会看到 Figure 2 所示输出。
这种行为对付 C++ 措施员来说好像有些怪异。它意味着在派生类被初始化之前,你可以挪用某个派生范例的虚拟函数——也就是说在其结构函数运行之前。同样,假如你从基类析构函数中挪用虚函数,该函数是在派生类被销毁之后运行的——也就是说在析构函数被挪用之后。那么先不说这种不同存在的原因,适才不是还说从结构函数/析构函数中挪用虚函数被认为是糟糕的实践。
为什么微软的家伙们要像这样来设计 C# 呢?因为它简化了内存打点。垃圾收集器为了释放内存,它需要知道工具有多大。假如 C# 像 C++ 那样结构工具,那么你大概会遇到这样一种环境:有两个工具,Obj1 和 Obj2,下面这两条语句都为真:
typeof(Obj1)==typeof(Obj2)
sizeof(Obj1)!= sizeof(Obj2)
因为工具之一是被部门结构。(不要忘了垃圾收集器是异步运行的。)通过将工具结构成最终范例,垃圾收集器能从其范例抉择工具的巨细。假如 C# 像 C++ 那样举办部门结构,则垃圾收集器将需要更多的代码来抉择部门结构工具的真实巨细。这样将带来巨大性和机能下降,首先要办理这个问题很让人气馁,所觉得了较快的垃圾收集好处,微软的家伙们抉择像上面那样来实现 C#。有关这方面的接头拜见 Raymond Chen 的 blog:“The Old New Thing”。
在 2004 三月的专栏中,你展示了如何改变文件打开对话框的最新视图状态配置,但没有涉及到生存这个用户利用的最新视图配置。我碰着的问题是读取用户已有的打开文件对话框配置。我只找到直接读取列表框信息的要领,但当用户选择缩略图模式时,那样做不能获得正确的信息。对此你有没有办理步伐?
我正在用民众的 CFileDialog 类做开拓,应该不是很难,但工作好像并不是那样。我想强制文件打开对话框的视图模式为缩略图。我要用 Visual C++ 来做,你可否提供一些发起?
有几个读者都在问文件打开对话框中的缩略图问题。在我三月份的专栏中,我示范了假如向文件打开对话框中的 SHELLDLL_DefView 专用窗口发送 WM_COMMAND 动静以配置差异的视图模式——但你如何知道当前所处的模式是哪一个呢?你必需获取列表控件并挪用 CListCtrl::GetView:
// in dialog class
HWND hlc = ::FindWindowEx(m_hWnd,
NULL, _T("SysListView32"), NULL);
CListCtrl* plc = (CListCtrl*)CWnd::FromHandle(hlc);
DWORD dwView = plc->GetView();
CListCtrl::GetView 返回 LV_XXX 代码之一,但正像 Maarten 发明的那样,Windows 对图标模式和缩略图模式都返回 LV_VIEW_ICON。
那么如何区分到底是哪种视图模式呢?我绞尽脑汁并钻进头文件查找,最后发明一个叫 LVM_GETITEMSPACING 的动静,该动静是作什么用的呢——用来获取图标隔断。顾名思义,图标隔断是图标视图模式中图标之间的像素隔断。LVM_GETITEMSPACING 不是很好利用,以至于 MFC 都没有对之举办包装(好比说 MFC 中并没有 CListCtrl::GetIconSpacing 这样的函数)。所以在 MFC 中你得本身发送动静:
CSize sz = CSize(plc->SendMessage(LVM_GETITEMSPACING));
Windows 凭据凡是方法返回尺寸,在高位和低位字中编码的 cx/cy,然后CSize很规矩地为你举办解码。一旦有了图标隔断,你便可以将它与 GetSystemMetrics(SM_CXICONSPACING) 返回的系统隔断值举办较量。假如列表视图的图标隔断与系统的一样,则视图是图标模式。假如大于系统隔断,则视图为缩略图模式:
#p#分页标题#e#
if (sz.cx > GetSystemMetrics(SM_CXICONSPACING)) {
// thumbnail view
} else {
// icon view
}
讲了那么多缩略图,接下来的问题是如何一连化差异用户会话的视图状态?对此,当措施终止时,你需要用 Profile 函数在用户设置文件中生存最后利用的模式,并在下一次启动措施时再次规复它。我写了一个小示范措施,DlgTest。措施利用了一个实现一连化措施行为的类 CPersistOpenDlg。这个类又借助别的一个类 CListViewShellWnd,用它来封装 SHELLDLL_DefView 窗口(拜见三月份专栏)。CListViewShellWnd 包括获取和配置视图模式的函数,由这些函数来区分图标和缩略图模式:
CListViewShellWnd m_wndLVSW;
…
m_wndLVSW.SetViewMode(ODM_VIEW_THUMBS);
CListViewShellWnd 的 OnDestroy 处理惩罚器在某个数据成员 m_lastViewMode 中生存视图模式。当对话框被销毁时,CPersistOpenDlg 的析构函数挪用 WriteProfileInt 将这个值写入用户设置文件。对话框启动时,CPersistOpenDlg 给本身送一个初始化动静;该动静处理惩罚例程挪用 GetProfileInt 从磁盘读取存储在设置文件中的值并配置视图模式。PostMessage 是必需挪用的,因为通例初始化动静 WM_INITDIALOG 和 CDN_INITDONE 在文件对话框被完全初始化之前就会到来——有关这一点的表明拜见三月份专栏。
顺便说一下,任何时候你都应该利用 GetProfileXxx 和 WriteProfileXxx 来一连化应用措施的配置。MFC 用 CWinApp 包装了这些函数。假如你在应用措施启动时挪用(一般都是在 InitInstance 函数中) CMyApp::SetRegistryKey("KeyName"),MFC 利用注册表来存储用户设置信息,而不是 INI 文件。下面是 DlgTest 用的 INI 文件:
[settings]
ViewMode=28717
偶然在一些文字资料和 C++ 文档以及 Microsoft .NET 框架中看到术语“POD 范例”。这个术语是什么意思?
你可以将 POD 范例看作是一种来自外太空的用绿色掩护层包装的数据范例,POD 意为“Plain Old Data”(译者:假如必然要译成中文,那就叫“彻头彻尾的老数据”怎么样!)这就是 POD 范例的寄义。其确切界说相当粗拙(拜见 C++ ISO 尺度),其根基意思是 POD 范例包括与 C 兼容的原始数据。譬喻,布局和整型是 POD 范例,但带有结构函数或虚拟函数的类则不是。 POD 范例没有虚拟函数,基类,用户界说的结构函数,拷贝结构,赋值操纵符或析构函数。
为了将 POD 范例观念化,你可以通过拷贝其比特来拷贝它们。另外, POD 范例可以长短初始化的。譬喻:
struct RECT r; // value undefined
POINT *ppoints = new POINT[100]; // ditto
CString s; // calls ctor ==> not POD
非 POD 范例凡是需要初始化,岂论是挪用缺省的结构函数(编译器提供的)照旧本身写的结构函数。
已往, POD 对付编写编译器或与C 兼容的 C++ 措施的人来说很重要。此刻,POD 来到 .NET 的情况中。在托管 C++ 中,托管范例(包罗 __value 和 __gc 两者)能包括嵌入的原生 POD 范例。 Figure 3 展示了例举说明代码。托管的 Circle 类能包括 POINT,但无法包括 CPoint 类。假如你实验编译 pod.cpp 会报一个 C3633 错误:“Cannot define ”m_center” as a member of managed ”Circle” because of the presence of default constructor ”CPoint::CPoint” on class ”CPoint”.”(译者:意思是由于类 CPoint 有缺省的结构函数‘CPoint::CPoint’,所以不能将‘m_center’界说为托管类‘Circle’的一个成员)
.NET 限定嵌入的当地工具只能为 POD 范例的来由是这样做能安详地拷贝它们,不消担忧挪用结构函数,初始化虚表,或任何非 POD 范例需要的其它机制。