C++ 比较运算符中的共享指针常数==

C++ 比较运算符中的共享指针常数==,c++,constants,shared-ptr,comparison-operators,C++,Constants,Shared Ptr,Comparison Operators,我无意中发现了我正在使用的共享指针的意外行为 共享指针实现引用计数,并在必要时分离(例如制作副本)非常量使用中包含的实例。 为此,对于每个getter函数,智能指针都有一个const和一个non-const版本,例如:运算符T*()和运算符T const*()const 问题:将指针值与nullptr进行比较会导致分离 预期:我认为比较运算符将始终调用const版本 简化示例: (此实现没有引用计数,但仍然显示问题) (因为我为什么要比较非常量指针?) 其中应调用: inline operato

我无意中发现了我正在使用的共享指针的意外行为

共享指针实现引用计数,并在必要时分离(例如制作副本)非常量使用中包含的实例。
为此,对于每个getter函数,智能指针都有一个
const
和一个
non-const
版本,例如:
运算符T*()
运算符T const*()const

问题:将指针值与
nullptr
进行比较会导致分离

预期:我认为比较运算符将始终调用const版本

简化示例:
(此实现没有引用计数,但仍然显示问题)

(因为我为什么要比较非常量指针?)

其中应调用:

inline operator const T *() const 
其他问题:

  • 为什么默认情况下不使用const运算符?
    • 这是因为不能仅通过返回值的类型来选择函数吗?
      =>这一点在中得到了回答
所以这个问题可以归结为:

  • 为什么比较运算符的默认实现不将参数作为常量引用,然后调用常量函数
  • 你能引用一个C++引用吗?< /LI>
当常量和非常量上存在重载时,如果您使用的对象是非常量,编译器将始终调用非常量版本。否则,何时调用非常量版本

如果要显式使用常量版本,请通过常量引用调用它们:

const SharedPointer<int>& constRef = testInst;
eq = nullptr == constRef;
这种理解是错误的。操作符的重载解析相当复杂,内置版本的操作符有一大组代理签名参与解析。标准中的[over.match.oper]和[over.build]中描述了此过程

具体而言,[over.build]第16页和第17页定义了相关的内置平等候选者。这些规则表示,对于每种指针类型
T
,都存在一个
操作符==(T,T)
。现在,
int*
const int*
都是指针类型,因此两个相关的签名是
operator==(int*,int*)
operator==(const int*,const int*)
。(还有
操作符==(std::nullptr\u t,std::nullptr\u t)
,但它不会被选中。)


为了区分这两个重载,编译器必须比较转换序列。对于第一个参数,
nullptr\u t->int*
nullptr\u t->const int*
都是相同的;它们是指针转换。将
常量
添加到其中一个指针将被包括在内。(参见[conv.ptr])对于第二个参数,转换分别是
SharedPointer->int*
SharedPointer->const int*
。第一个是用户定义的转换,调用
操作符int*()
,无需进一步转换。第二种是用户定义的转换,调用
运算符const int*()const
,这需要首先进行限定转换,以便调用
const
版本。因此,首选非常量版本。

当常量和非常量上存在重载时,如果您使用的对象是非常量,编译器将始终调用非常量版本。否则,何时调用非常量版本

如果要显式使用常量版本,请通过常量引用调用它们:

const SharedPointer<int>& constRef = testInst;
eq = nullptr == constRef;
这种理解是错误的。操作符的重载解析相当复杂,内置版本的操作符有一大组代理签名参与解析。标准中的[over.match.oper]和[over.build]中描述了此过程

具体而言,[over.build]第16页和第17页定义了相关的内置平等候选者。这些规则表示,对于每种指针类型
T
,都存在一个
操作符==(T,T)
。现在,
int*
const int*
都是指针类型,因此两个相关的签名是
operator==(int*,int*)
operator==(const int*,const int*)
。(还有
操作符==(std::nullptr\u t,std::nullptr\u t)
,但它不会被选中。)


为了区分这两个重载,编译器必须比较转换序列。对于第一个参数,
nullptr\u t->int*
nullptr\u t->const int*
都是相同的;它们是指针转换。将
常量
添加到其中一个指针将被包括在内。(参见[conv.ptr])对于第二个参数,转换分别是
SharedPointer->int*
SharedPointer->const int*
。第一个是用户定义的转换,调用
操作符int*()
,无需进一步转换。第二种是用户定义的转换,调用
运算符const int*()const
,这需要首先进行限定转换,以便调用
const
版本。因此,首选非常量版本。

这是因为表达式
testInst==nullptr
是如何解析的:

  • 让我们看看类型:
    testInst
    属于
    SharedPointer
    类型
    nullptr
    是(为了简化)类型
    T*
    void*
    ,具体取决于用例。
    因此表达式读取
    SharedPointer==int*
  • 我们需要有相等的类型来调用比较运算符。有两种可能性:
  • 解析为
    int*==int*

    这涉及到调用
    操作符int*()
    操作符int const*()const

    [需要引用]
  • 解析为
    SharedPointer==SharedPointer

    这个
    const SharedPointer<int>& constRef = testInst;
    eq = nullptr == constRef;
    
    bool operator==(T const* a, T const* b)
    
    class X {
       public:
          operator int * () { std::cout << "1\n"; return nullptr; }
          operator const int * () { std::cout << "2\n"; return nullptr; }
          operator int * () const { std::cout << "3\n"; return nullptr; }
          operator const int * () const { std::cout << "4\n"; return nullptr; }
    };
    
    int main() {
       X x;
       const X & rcx = x;
    
       int* pi1 = x;
       const int* pi2 = x;
       int* pi3 = rcx;
       const int* pi4 = rcx;
    }
    
    1
    2
    3
    4