如何将CRect和CRect*都作为GetClientRect的输入参数?

3

初学者提问。

在 Win32API 中,::GetClientRect 的第二个参数是 LPRECT,但它可以接收 CRectCRect*

我试图查看 LPRECT 的定义,但它似乎只是一个普通的结构体指针:

typedef struct tagRECT
{
    LONG    left;
    LONG    top;
    LONG    right;
    LONG    bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;

CRect 继承了 tagRECT

class CRect :public tagRECT {...}

使用MFC,CWnd::GetClientRect的用法相同:
CRect rect;
GetClientRect(&rect); // works
GetClientRect(rect);  // works too.

这怎么可能呢?如果我错过了一些C++的基本概念,请告诉我。

10
CRect类有一个operator LPRECT()类型转换函数,将其服务于单个成员。 - WhozCraig
供日后参考)一个简单的示例 - starriet
1个回答

7

在进行重载1解析期间,编译器会构建一组与参数匹配的候选函数。如果没有任何一个函数的函数签名与参数完全匹配,则编译器将尝试应用隐式转换,每个参数最多只应用一次。

这就是使得两个调用都变成可能的原因,虽然每种情况下的隐式转换不同:

GetClientRect(&rect);

在此函数调用表达式中,&rect 的类型为CRect*,其中CRect公开继承自RECT,因此从CRect*RECT*(又名LPRECT)的隐式指针转换会被执行。

GetClientRect(rect);

在这里,rect的类型是CRect。C++没有提供从CRectRECT*的任何内置转换,因此会考虑用户定义的转换。具体而言,有一个转换运算符CRect::operator LPRECT,它可以在给定CRect对象的情况下产生一个RECT*指针。
至关重要的是要认识到,这两个函数调用表达式做的事情是不同的。在第二种情况下,编译器在调用GetClientRect()之前实际上注入了一次对operator LPRECT()的调用。在语言中没有什么强制规定转换运算符必须按某种方式行事,这取决于库作者是否提供合理的实现。
在这种情况下,operator LPRECT()的行为符合预期,两个函数调用具有相同的可观察行为。既然有两种方法可以做同样的事情,就需要指导以做出明智的选择:
虽然没有严格的规则可以使用哪个选项,但这取决于个人观点。我建议使用GetClientRect(&rect),原因如下:
  • 对代码读者更容易理解
    • 指针参数经常用作[out]参数,实现将其结果写入其中
    • 不那么模糊: GetClientRect(rect)看起来像是按值传递,或者可能对象被绑定到引用上。实际上,它既不是,也不是(隐形的)转换运算符被调用。
  • 与使用转换运算符的版本一样不会更昂贵

1重载决议是一种核心语言特性。推动此部分语言的规则集,然而,使它远非基础知识。这是您最终必须学习的东西,但是它是一个非常陡峭的学习曲线。cppreference.com链接为您展示了所涉及的复杂性。


所以,在两个调用中,没有一个函数的签名与参数完全匹配,因此编译器尝试“隐式转换”,但方式不同,对吗?另一个独立的问题:为什么是“每个参数最多一个”?如果编译器尝试多次隐式转换直到找到与函数签名匹配的正确匹配,是否会有任何问题?感谢您提供这个很棒的答案! - starriet
2
@starriet 正确,&rectrect都不是 RECT*类型,因此两种情况都需要进行转换。是的,这些转换是不同的(但可能使用优化后编译成完全相同的机器代码)。编译器将尝试最多一次转换。这只是语言规范的一部分。我不知道这个规则背后的原因,不过我猜想否则重载集会呈指数级增长。在 1980 年代编写编译器时,你肯定不希望遇到这样的事情。另外,它还会引入需要处理的歧义。 - IInspectable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接