【问题标题】:Why does CDC::SelectObject(CFont*) accept a CFont object vs. a pointer?为什么 CDC::SelectObject(CFont*) 接受 CFont 对象与指针?
【发布时间】:2023-03-23 06:33:01
【问题描述】:
    CPaintDC dc(this); 
    CFont font; 
    dc.SelectObject(font);  // why does this build? 

函数CDC::SelectObject 接受一个类型为CFont 的指针,但为什么它需要提供一个对象呢?我遇到了这个问题,上面的代码是不可预测的,有时会崩溃,但并非总是如此。

【问题讨论】:

  • 因为CFont::operator HFONT() const;
  • 为什么这个问题被投票关闭和否决?这是一个完全合法的问题,是一个不容易猜到的答案。
  • @SergeyA 正是我的想法,谢谢你这么说。

标签: c++ mfc


【解决方案1】:

有问题的代码是有效的,有点。它的编译是由于两件事的结合:

当编译器尝试匹配 dc.SelectObject(font) 的重载时,它们都不匹配。所以接下来它会尝试返回HFONT 的用户定义的转换运算符(operator HFONT())。这与采用HGDIOBJ 的未记录重载相匹配(HGDIOBJHFONT 的类型定义为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 空闲时间处理的一部分进行清理。

【讨论】:

  • 这似乎是一个更好的解释。我将按照代码示例的工作原理添加我自己的答案。
【解决方案2】:

我同意 IInspectable 的回答,这是我自己的概念证明:

#include "stdafx.h"
#include <iostream>

class A {
    int a_int = 6;
    char* a_carp = "A derived";
public:
    operator int() { return a_int; }
    operator char*() { return a_carp; }
};

class B : public A {
    float b_float = 5.7;
    char* b_carp = "B Derived";
public:
    operator float() { return b_float; }
    operator char*() { return b_carp; }
};

void Print(char * text)
{
    std::cout << text << std::endl;
}

int main() {
    B b_obj;
    //  long a = b_obj;
    char* c_p = b_obj;

    Print(b_obj);

    return 0;
}

以上代码构建,输出为“B Derived”。

它是如何工作的?

当我们使用一个对象代替不同的类型时,该对象会寻找合适的转换运算符。如果它找到它,它会应用它并为你转换它。

在本例中,该运算符为HFONT()。重要的是要注意运算符的实际名称并不重要,重要的是返回类型/签名。所以当它找到一个匹配(正确的签名)时,它就会调用它。

为什么会有不可预测的行为?

我也清楚这一点,因为我在 OP 中提到了这一点。我发布的代码与构建问题有关,但实际代码是这样的:

CFont* pOldFont = (CFont*) dc.SelectObject(font); 

问题是强制类型转换(CFont*)。如果我们将HGDIOBJ 传递给SelectObject(),它会返回HGDIOBJ,但它被强制类型转换为CFont *,这就是问题所在,所以使用转换运算符没有其他问题。

【讨论】:

  • 不过,实际情况并非如此。 MFC 是一个 C++ 库,但问题不在于类层次结构。这是关于CDC 暴露底层C 接口,C 中没有类层次结构。它提供SelectObject 重载,取HGDIOBJHGDIOBJHFONT 是相同底层类型的不同名称。
  • @IInspectable 我展示的类层次结构只是添加了更多细节,其对象的类型转换被调用,但我确信它是一个类的东西。
  • 您的问题中不存在类型转换。您问题中的代码(几乎)是正确的,除了在对象仍被选入 DC 时将其销毁。您必须在 font 超出范围之前将旧字体选择到 DC 中。要修复此代码,您可以简单地存储从 SelectObject 返回的 HGDIOBJ,或将其附加到另一个 CFont 实例:CFont oldFont; oldFont.Attach(dc.SelectObject(font)); ... dc.SelectObject(oldFont.Detach();
猜你喜欢
  • 2015-12-05
  • 1970-01-01
  • 2020-06-22
  • 2011-02-08
  • 2012-05-04
  • 2010-09-18
  • 2010-12-07
  • 1970-01-01
相关资源
最近更新 更多