|
C++ Q&A 专栏... 原代码下载:CQA0411.exe (234KB)
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”。
Maarten van Dillen
我正在用公共的 CFileDialog
类做开发,应该不是很难,但事情似乎并不是那样。我想强制文件打开对话框的视图模式为缩略图。我要用 Visual C++ 来做,你能否提供一些建议?
Elliot Leonard
// 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。 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 在文件对话框被完全初始化之前就会到来——有关这一点的解释参见三月份专栏。 [settings] ViewMode=28717
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 类型通常需要初始化,不论是调用缺省的构造函数(编译器提供的)还是自己写的构造函数。 |
作者简介Paul DiLascia 是一名自由作家,顾问和 Web/UI 设计者。他是《Writing Reusable Windows Code in C++》书(Addison-Wesley, 1992)的作者。通过 http://www.dilascia.com 可以获得更多了解。 |
| 本文出自 MSDN Magazine 的 November 2004 期刊,可通过当地报摊获得,或者最好是 订阅 |
背景:
阅读新闻
调用虚拟函数,持续化视图状态,POD 类型概念
| [日期:2005-12-30] | 作者: | [字体:大 中 小] |
阅读: 次
【 打印 】
【 打印 】
相关新闻
相关关键词:
全站导航

确实如此,在这个方面 C# 与 C++ 是有差别的。在 C++
中,如果你从构造函数或者析构函数中调用虚拟函数,编译器调用的虚拟函数是定义在这个正在被构造的类实例中的(例如,如果从 Base::Base 中调用
Base::SomeVirtFn ),不是最底层派生的实例(the most derived
instance),正像你说的那样,因为在最底层派生的构造函数执行之前,虚表还没有完全被初始化。另一种说法是派生类还没有被创建。
作者简介
gmail.com