将成员功能更改为稳定破坏代码

Change member function to const silently breaks code

本文关键字:代码 成员 功能      更新时间:2023-10-16

我正在为第三方库写一个接口。它通过基本上是void*的C接口来操纵对象。这是简化的代码:

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);
}

一切都很好,直到我在operator void *()线上添加const。然后,代码默默默认为使用operator void **()破坏代码。

我的问题是为什么?

我正在通过不修改对象的函数返回指针。应该能够将其标记为const。如果将其更改为const指针,则编译器应该错误,因为operator void **()不应该是只需void *的函数匹配的匹配。

这是标准所说的应该发生的事情,但这远非显而易见。对于快速读者,跳到"如何修复它?"最后。

了解为什么const重要

添加const预选赛后,当您使用LibIntf实例调用UseMe时,编译器将具有以下两个可能性:

  1. LibIntf 1 LibIntf 2 void** 3 void*(通过 operator void**()
  2. LibIntf 3 const LibIntf 2 void* 1 void*(通过 operator void* const()

1)无需转换。
2)用户定义的转换操作员。
3)法律转换。

这两个转换路径是合法的,所以选择哪一个?
定义C 答案的标准:

[over.match.best]/1

定义ICSi(F)如下:

  • [...]
  • ICSi(F)表示隐式转换序列,该顺序将列表中的i参数转换为可行函数的i TH参数F。[over.best.ics]定义了隐式转换序列,[over.ics.rank]定义了对一个隐式转换序列的含义,比另一个更好的转换顺序或更差的转换顺序。

鉴于这些定义,可行函数F1被定义为 与其他可行函数F2更好的功能,如果所有参数 iICSi(F1)不比ICSi(F2)更差的转换序列,然后是

  • 对于某些参数jICSj(F1)是比ICSj(F2)更好的转换序列,或者,如果不是,

  • 上下文是用户定义的转换的初始化(请参阅[dcl.init][over.match.conv][over.match.ref]),并且是从F1的返回类型到目标类型的标准转换序列(即,实体的类型是初始化的实体类型)是从F2的返回类型到目标类型的标准转换序列更好的转换序列。

(我必须在获得几次之前阅读几次。)

这一切都意味着在您的特定情况下 比选项#1比选项#2更好更好,因为对于用户定义的转换操作员,返回类型的转换(void**)在选项#1中的void*)在之后考虑参数类型的转换( LibIntf to CC_46 to const LibIntf in Option#2中)。

在链中,这意味着在选项#1中没有任何转换(后者在转换链中,但尚未考虑),但是在选项#2中,需要从non-const转换为const。选项#1因此被称为更好

如何修复它?

只需删除将non-const考虑到const 转换 by casting const(显式(始终称为转换(或称为转换))):):

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);
    // unfortunately, you cannot const_cast an instance, only refs & ptrs
    return UseMe(static_cast<const LibIntf>(lib));
}