我正在为第三方库编写接口。它通过C接口操作对象,C接口本质上是void*
。这是简化的代码:
struct LibIntf
{
LibIntf() : opaquePtr{nullptr} {}
operator void *() /* const */ { return opaquePtr; }
operator void **() { return &opaquePtr; }
void *opaquePtr;
};
int UseMe(void *ptr)
{
if (ptr == (void *)0x100)
return 1;
return 0;
}
void CreateMe(void **ptr)
{
*ptr = (void *)0x100;
}
int main()
{
LibIntf lib;
CreateMe(lib);
return UseMe(lib);
}
在const
行添加operator void *()
之前,一切正常。然后,代码默认默认使用operator void **()
破解代码。
我的问题是为什么?
我通过一个不修改对象的函数返回一个指针。应该能够标记const
。如果将其更改为const
指针,编译器应该会出错,因为operator void **()
不应该只是想要CallMe()
的函数void *
的匹配。< / p>
答案 0 :(得分:6)
这是标准所说的应该发生的事情,但这远非显而易见。对于快速读者,请跳转到“如何解决它?”最后。
const
重要的原因添加const
限定符后,当您使用UseMe
实例调用LibIntf
时,编译器具有以下两种可能性:
LibIntf
→ 1 LibIntf
→ 2 void**
→ 3 void*
(通过operator void**()
)LibIntf
→ 3 const LibIntf
→ 2 void*
→ 1 void*
(通过operator void* const()
) 1)无需转换。
2)用户定义的转换运算符
3)法律转换。
这两个转换路径是合法的,那么选择哪一个?
定义C ++答案的标准:
[over.match.best]/1
如下定义
ICSi(F)
:
- [...]
- let
ICSi(F)
表示隐式转换序列,它将列表中的i
参数转换为可行函数F的i
参数的类型。[over.best.ics]
定义隐式转换序列和[over.ics.rank]
定义了一个隐式转换序列比另一个转换序列更好的转换序列或更差的转换序列。鉴于这些定义,可行函数
F1
被定义为a 如果适用于所有参数,则比另一个可行函数F2
具有更好的功能i
,ICSi(F1)
转换序列不比ICSi(F2)
差,然后
对于某些参数
j
,ICSj(F1)
是比ICSj(F2)
更好的转换序列,或者,如果没有,上下文是由用户定义的转换初始化(请参阅
[dcl.init]
,[over.match.conv]
和[over.match.ref]
)以及来自{{1的返回类型的标准转换序列到目标类型(即,正在初始化的实体的类型)是比从F1
的返回类型到目标类型的标准转换序列更好的转换序列。
(在获得它之前我必须阅读它几次。)
这意味着在您的特定情况下比选项#1 更好而不是选项#2,因为对于用户定义的转换运算符,返回类型的转换({在选项#2中将参数类型( 在链中,这意味着在选项#1中没有任何内容可以转换(后者在转换链中会有,但尚未考虑)但在选项#2中,从 通过将变量转换为F2
转换为void**
后,{1}}到选项#1中的void*
被视为。< / p>
LibIntf
转换为{{1需要。因此,选项#1被称为更好。如何解决?
const LibIntf
(显式(强制转换),无需考虑non-const
至const
转换总是明确的(或被称为转换))):non-const