分享
 
 
 

调用虚拟函数,持续化视图状态,POD 类型概念

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

C++ Q&A 专栏...

调用虚拟函数,持续化视图状态,POD 类型概念

原著:Paul DiLascia

翻译:Northtibet

原代码下载:CQA0411.exe (234KB)

原文出处:MSDN Magazine November 2004 (C++ Q&A)

调用虚拟函数

持续化视图状态

POD 类型概念

在 C++

中,无法从某个类的构造函数中调用派生的虚拟函数,因为虚表还没有完全建立。但是在C#中好像就可以,是这样吗?为什么会有这种差别呢?

Clifton F. Vaughn

确实如此,在这个方面 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 三月的专栏中,你展示了如何改变文件打开对话框的最新视图状态设置,但没有涉及到保存这个用户使用的最新视图设置。我遇到的问题是读取用户已有的打开文件对话框设置。我只找到直接读取列表框信息的方法,但当用户选择缩略图模式时,那样做不能得到正确的信息。对此你有没有解决办法?

Maarten van Dillen

我正在用公共的 CFileDialog

类做开发,应该不是很难,但事情似乎并不是那样。我想强制文件打开对话框的视图模式为缩略图。我要用 Visual C++ 来做,你能否提供一些建议?

Elliot Leonard

有几个读者都在问文件打开对话框中的缩略图问题。在我三月份的专栏中,我示范了如果向文件打开对话框中的 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)

返回的系统间隔值进行比较。如果列表视图的图标间隔与系统的一样,则视图是图标模式。如果大于系统间隔,则视图为缩略图模式:

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 类型”。这个术语是什么意思?

Shelby Nagwitz

你可以将 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

类型需要的其它机制。

向 Paul 提问和评论请发到 cppqa@microsoft.com.

作者简介

Paul DiLascia 是一名自由作家,顾问和 Web/UI 设计者。他是《Writing Reusable Windows Code in

C++》书(Addison-Wesley, 1992)的作者。通过 http://www.dilascia.com 可以获得更多了解。

本文出自 MSDN Magazine

November 2004 期刊,可通过当地报摊获得,或者最好是

订阅

本文由 VCKBASE MTT 翻译

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有