有问题的代码是有效的,有点。它的编译是由于两件事的结合:
当编译器尝试匹配 dc.SelectObject(font) 的重载时,它们都不匹配。所以接下来它会尝试返回HFONT 的用户定义的转换运算符(operator HFONT())。这与采用HGDIOBJ 的未记录重载相匹配(HGDIOBJ 和HFONT 的类型定义为void*)。
问题中发布的代码也几乎是正确的,但有两个例外:
-
font 对象在仍被选入设备上下文时被销毁。虽然这会导致双重删除错误(字体对象由 CFont 实例和设备上下文拥有),但在传递无效句柄时,对 DeleteFont 的调用会正常失败。
- 先前选择到设备上下文中的字体对象丢失,从而泄露。
这两个问题都不会导致不可预知的行为或间歇性崩溃。正如your answer 中解释的那样,真正的代码看起来是这样的:
CFont* pOldFont = (CFont*) dc.SelectObject(font);
这是一个表现出未定义行为的错误。 dc.SelectObject(font) 返回一个HGDIOBJ(typedef'd 为void*),随后将其转换为不相关的类型(CFont*)。虽然将先前选择的字体存储到设备上下文中以便以后恢复它是正确的,但代码不是。尊重所有权的实现可能是:
CPaintDC dc(this);
CFont font;
CFont oldFont;
// Transfer ownership of font to the DC, and the previously selected font into oldFont
oldFont.Attach(dc.SelectObject(font.Detach()));
// Use dc
// Transfer ownership back
font.Attach(dc.SelectObject(oldFont.Detach()));
// oldFont goes out of scope; this a no-op since it no longer owns any resources
// font goes out of scope, releasing all resources owned by it
// dc goes out of scope, releasing all resources owned by objects selected into it
如果您愿意暂时牺牲严格的所有权语义,您可以使用更标准的实现来让您的生活更轻松:
CPaintDC dc(this);
CFont font;
CFont* pOldFont = dc.SelectObject(&font);
// Use dc
dc.SelectObject(pOldFont);
这是安全的,即使您提前退出,无需恢复设备上下文。它仍然会导致 CFont 实例和设备上下文(由 API 优雅地处理)拥有的字体对象的双重删除。不过,它并没有表现出字体泄漏,因为事情比看起来更复杂:这里涉及到另一个不可见的所有者,一个由 MFC 控制的存储临时对象的地图(比如从CGdiObject::FromHandle 返回的那些,SelectObject(CFont*)来电)。临时对象作为 MFC 空闲时间处理的一部分进行清理。