初始化C++类成员和在你的MFC应用中插手位置栏
副标题#e#
问题
我的问题是关于初始化C++类成员的。我见过很多这样的代码(包罗在你的栏目中也见到过):
CSomeClass::CSomeClass()
{
x=0;
y=1;
}
而在此外什么处所则写成下面的样子:
CSomeClass::CSomeClass() : x(0), y(1)
{
}
我的一些措施员伴侣说第二种要领较量好,但他们都不知道为什么是这样。你能汇报我这两种类成员初始化要领的区别吗?
答复
从技能上说,你的措施员伴侣是对的,可是在大大都环境下,两者实际上没有区别。有两个原因使得我们选择第二种语法,它被称为成员初始化列表:一个原因是必需的,另一个只是出于效率思量。
让我们先看一下第一个原因——须要性。设想你有一个类成员,它自己是一个类可能布局,并且只有一个带一个参数的结构函数。
class CMember {
public:
CMember(int x) { ... }
};
因为Cmember有一个显式声明的结构函数,编译器不发生一个缺省结构函数(不带参数),所以没有一个整数就无法建设Cmember的一个实例。
CMember* pm = new CMember; // Error!!
CMember* pm = new CMember(2); // OK
假如Cmember是另一个类的成员,你奈何初始化它呢?你必需利用成员初始化列表。
class CMyClass {
CMember m_member;
public:
CMyClass();
};
//必需利用成员初始化列表
CMyClass::CMyClass() : m_member(2)
{
•••
}
没有其它步伐将参数通报给m_member,假如成员是一个常量工具可能引用也是一样。按照C++的法则,常量工具和引用不能被赋值,它们只能被初始化。
#p#副标题#e#
第二个原因是出于效率思量,当成员类具有一个缺省的结构函数和一个赋值操纵符时。MFC的Cstring提供了一个完美的例子。假定你有一个类CmyClass具有一个Cstring范例的成员m_str,你想把它初始化为"yada yada."。你有两种选择:
CMyClass::CMyClass() {
// 利用赋值操纵符
// CString::operator=(LPCTSTR);
m_str = _T("yada yada");
}
//利用类成员列表
// and constructor CString::CString(LPCTSTR)
CMyClass::CMyClass() : m_str(_T("yada yada"))
{
}
在它们之间有什么差异吗?是的。编译器老是确保所有成员工具在结构函数体执行之前初始化,因此在第一个例子中编译的代码将挪用CString::Cstring来初始化m_str,这在节制达到赋值语句前完成。在第二个例子中编译器发生一个对CString:: CString(LPCTSTR)的挪用并将"yada yada"通报给这个函数。功效是在第一个例子中挪用了两个Cstring函数(结构函数和赋值操纵符),而在第二个例子中只挪用了一个函数。在Cstring的例子里这是无所谓的,因为缺省结构函数是内联的,Cstring只是在需要时为字符串分派内存(即,当你实际赋值时)。可是,一般而言,反复的函数挪用是挥霍资源的,尤其是当结构函数和赋值操纵符分派内存的时候。在一些大的类内里,你大概拥有一个结构函数和一个赋值操纵符都要挪用同一个认真分派大量内存空间的Init函数。在这种环境下,你必需利用初始化列表,以制止不要的分派两次内存。在内部范譬喻ints可能longs可能其它没有结构函数的范例下,在初始化列表和在结构函数体内赋值这两种要领没有机能上的不同。不管用那一种要领,都只会有一次赋值产生。有些措施员说你应该老是用初始化列表以保持精采习惯,但我从没有发明按照需要在这两种要领之间转换有什么坚苦。在编程气势气魄上,我倾向于在主体中利用赋值,因为有更多的空间用来名目化和添加注释,你可以写出这样的语句:x=y=z=0;
可能memset(this,0,sizeof(this));
留意第二个片隔离对长短面向工具的。
当我思量初始化列表的问题时,有一个奇怪的特性我应该告诫你,它是关于C++初始化类成员的,它们是凭据声明的顺序初始化的,而不是凭据呈此刻初始化列表中的顺序。
class CMyClass {
CMyClass(int x, int y);
int m_x;
int m_y;
};
CMyClass::CMyClass(int i) : m_y(i), m_x(m_y)
{
}
你大概觉得上面的代码将会首先做m_y=I,然后做m_x=m_y,最后它们有沟通的值。可是编译器先初始化m_x,然后是m_y,,因为它们是按这样的顺序声明的。功效是m_x将有一个不行预测的值。我的例子设计来说明这一点,然而这种bug会越发自然的呈现。有两种要领制止它,一个是老是凭据你但愿它们被初始化的顺序声明成员,第二个是,假如你抉择利用初始化列表,老是凭据它们声明的顺序摆列这些成员。这将有助于消除夹杂。
问题
我方才在几台呆板上安装了Windows® 2000 Release Candidate 1,不知道奈何在我的MFC应用中获得具有新的Outlook气势气魄栏目标Open对话框(见图1)。
Figure 1 The New Open Dialog
#p#分页标题#e#
我可否只配置一个符号,可能我是否需要一个新的头文件和一个新的民众对话框的DDL?我留意到一些旧的应用措施如Notepad仿佛可以获得新的Open对话框而无须从头编译,但它们不是MFC应用。抱负环境,我但愿在Windows 9x 和Windows NT®下获得一个利用旧对话框的应用,而在Windows 2000下利用新的对话框。
Warren Stevens
答复
这个问题恐怕没有令你兴奋的复原。Windows 2000新的Open对话框是用一个新版本的commdlg.dll实现的,它在边上包括“Places”栏目。显示它的函数是GetOpenFileName,与在Windows 9x 和Windows NT®下利用的沟通。然而,GetOpenFileName此刻利用一个新版本的OPENFILENAME,这是一个在你的应用和对话框之间通报信息的布局。新的布局有一些特另外成员:
typedef struct tagOFN {
DWORD lStructSize; // 很重要!
•••
// 正想你老是知道而且喜欢的那样
#if (_WIN32_WINNT >= 0x0500)
void* pvReserved;
DWORD dwReserved;
DWORD FlagsEx;
#endif // (_WIN32_WINNT >= 0x0500)
} OPENFILENAME, *LPOPENFILENAME;
对,是这样。Windows 2000是Windows的第5个版本,用16进制暗示是0x500。假如你用_WIN32_WINNT = 0x0500编译措施,OPENFILENAME就会获得3个新成员。前两个是保存的,第三个符号域,FlagsEx,有一个新的OFN_EX_NOPLACESBAR栏目,它屏蔽了Places栏目。Windows——可能更精确的说,commdlg.dll——利用OPENFILENAME第一个成员lStructSize来抉择显示谁人对话框,假如lStructSize是76(旧的巨细),Windows就运行旧的对话框;假如是76+3´4=88(新的巨细),它就运行新的对话框——这是友好的Redmondtonians最初汇报我的。在我的研究中间,我发明这并不是完整的图画。
可是在我具体说明之前,先让我们走马观花的看一下MFC,接头别的一个问题。在MFC应用中,你并不常常直接挪用GetOpenFileName,而是利用CfileDialog——可能,框架利用CfileDialog。当用户挪用File | Open,节制稀里哗啦的一路颠末CWinApp::OnFileOpen和几个其它的函数,最终达到CDocManager::DoPromptFileName,这个函数建设一个CfileDialog。CfileDialog具有一个OPENFILENAME布局的数据成员:
class CFileDialog : public CCommonDialog {
OPENFILENAME m_ofn;
•••
};
这个布局的巨细是当友好的Redmondtonians 编译MFC42.DLL 时OPENFILENAME的巨细;换句话说,旧的巨细。并且,假如你正在举办一个静态毗连,MFC代码在MFC42.DLL或NAFXCW.LIB里是冻结的,你不能仅仅配置m_ofn.lStructSize为新的巨细,因为CfileDialog除m_ofn外尚有其它数据成员,它们将被新的OPENFILENAME的成员包围。不再延误了,我开始利用极度的要领避开这个问题。我思量可以做些什么,雷同于MFC中利用CpropertyPage那样。PROPSHEETPAGE和PROPSHEETHEADER的巨细在从Windows 95到Windows 98的进程中的某处增加了,这是为了支持wizard气势气魄的页面。为了支持新膨胀的布局,MFC提供了CpropertyPageEx和CpropertySheetEx。最初的类(不带Ex的)仍然利用旧的布局;而新的类利用新的布局。这是一种杂凑,尤其是因为afxdlgs.h具有本身的旧的布局的界说(AFX_ OLDPROPSHEETPAGE和AFX_OLDPROPSHEETHEADER),可是这样却行得通。
我对CfileDialog做了同样的工作。首先我派生一个新的CfileDialogEx类,它带有一个新的m_ofn,包括着新的OPENFILENAMEEX布局,我仿照0x500版本加以界说。我插手这3个新的成员而且利用m_ofn.重写了CfileDialog函数。不幸的是,因为大大都的MFC代码是牢靠的,没有任何虚拟成果,这就意味着复制本来的整个类。可是我已经下了刻意。
在我认为已经找到了m_ofn呈现的所有处所今后,我重写了它,欢快奋兴的编译了我的代码(在Windows 98上),然后运行——功效发明我获得的仍是旧气势气魄的对话框。而起,有一个谜团我忘了思量:假如Windows 2000利用lStructSize来抉择运行谁人Open对话框,为什么Windows 98的应用措施(象Notepad)在Windows 2000下运行时获得了新的对话框呢?啊!随Windows 98呈现的NOTEPAD.EXE显然在lStructSize 上有旧的OPENFILENAME的巨细,因此Windows 2000必需利用lStructSize之外的某种对象来抉择运行谁人对话框。
#p#分页标题#e#
到这里,我抉择回过甚去从头思量问题。我将MFC放到一边,实验直接挪用GetOpenFileName。我重写了我的应用措施的OnFileOpen:
void CMyApp::OnFileOpen()
{
OPENFILENAME ofn; // older version
memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
int nResult = ::GetOpenFileName(&ofn);
}
因为贯串本操练,我利用了旧的0x400版本的SDK文件(因为我但愿应用措施既可以在Windows 2000上运行,也可以在Windows 9x上运行),ofn.lStructSize就有了旧的巨细。当我编译并运行时,我在Windows 98上获得了旧的对话框,而在Windows 2000上获得了新的对话框——就象Notepad一样!因此可以说,实际上,Windows 2000足够夺目标为旧的应用利用新的对话框——但不是旧的MFC应用。它毫无意义。一个MFC应用的差异之处在那边呢?
必然是符号。为了发明真相,我在OPENFILENAME布局中手工添加了差异的符号,直到我的措施发生了不带Places栏目标旧气势气魄的窗口。你瞧,当我为ofn.Flags插手符号OFN_ENABLEHOOK时,我的对话框回到了从前。我将此奇怪的行为陈诉给Redmondtonians,他们证实“这种行为是设计的”。
那么,Windows 2000判定OPENFILENAME的巨细以及对话框是否利用hook进程。假如OPENFILENAME有旧的巨细,Windows 2000利用OFN_ENABLEHOOK来抉择运行哪个对话框。假如OPENFILENAME利用hook进程(可能配置了ORN_ENABLETEMPLATE),Windows 2000凭据旧的气势气魄显示对话框;不然,显示新的对话框。这就表明白为什么MFC应用显示了旧的对话框——因为CfileDialog,就象所有MFC的公用对话框一样,利用hook进程。这是MFC将公用对话框嵌入它的动静映射系统的方法,它用同样的方法利用AfxWndProc嵌入其它的窗口。
此刻你看到了功效:你给疑惑了。在一个MFC应用中获得新气势气魄的对话框的独一的步伐是完全绕开CfileDialog,直接挪用GetOpenFileName,而且不利用hook进程。纵然你用新的SDK文件和WINVER = 0x500编译你的应用,你仍不能利用MFC,因为它的库和DLLs有旧的巨细。你可以利用WINVER = 0x500自行编译MFC,可是谁知道那将怎么样呢?并且假如你真的BUILD了新的MFC,你将不得不将新的DLL和你的应用一块宣布,给它起个差异的名字,因为你的新的MFC DLL必定不会与其它的但愿利用CFileDialog 和其它布局的旧的巨细的MFC应用兼容。可能,你可以从头生成MFC,然后静态的毗连,这将极大的增加你的可执行文件的巨细,假如你不从头实现Windows的话。
到截稿时间为止,我从Redmond传闻在即将宣布的新的Visual C++®中,将会有一个差异名字的新版本的MFC DLL。新版本的MFC将支持Windows 2000中呈现的新的UI和APIs。
同时,我将图2留给你研究,它取自最新的SDK文档,"Header File Conventions",提供了Windows版本问题的钥匙。它向你说明,为了到达某个版本的Windows和Microsoft® Internet Explorer的方针你必需利用SDK头文件界说哪些宏。尽力别跌倒在糊口的快车道上。