比较运算符==中的共享指针常量

时间:2018-03-08 08:31:46

标签: c++ const shared-ptr comparison-operators

我偶然发现了我使用的共享指针的意外行为。

共享指针实现引用计数和分离(例如复制),如果需要,包含非const使用的实例。
为实现此目的,对于每个getter函数,智能指针都有constnon-const版本,例如:operator T *()operator T const *() const

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

期望:我认为比较运算符总是会调用const版本。

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

#include <iostream>

template<typename T>
class SharedPointer
{
public:
    inline operator T *() { std::cout << "Detached"; return d; }
    inline operator const T *() const { std::cout << "Not detached"; return d; }
    inline T *data() { std::cout << "Detached"; return d; }
    inline const T *data() const { std::cout << "Not detached"; return d; }
    inline const T *constData() const { std::cout << "Not detached"; return d; }

    SharedPointer(T *_d) : d(_d) { }

private:
    T *d;
};


int main(int argc, char *argv[])
{
    SharedPointer<int> testInst(new int(0));

    bool eq;

    std::cout << "nullptr  == testInst: ";
    eq = nullptr == testInst;
    std::cout << std::endl;
    // Output: nullptr  == testInst: Detached

    std::cout << "nullptr  == testInst.data(): ";
    eq = nullptr == testInst.data();
    std::cout << std::endl;
    // Output: nullptr  == testInst.data(): Detached

    std::cout << "nullptr  == testInst.constData(): ";
    eq = nullptr == testInst.constData();
    std::cout << std::endl;
    // Output: nullptr  == testInst.constData(): Not detached
}

问题1:为什么在应该足以调用const版本时调用的非const版本的函数?

问题2:为什么可以仍然可以调用非const版本?不比较运算符(特别是与不可变nullptr相比)是否总是对const引用进行操作?

记录:
我使用的共享指针是Qt&#39; QSharedDataPointer持有QSharedData派生的实例,但这个问题不是Qt特定的。

修改

根据我的理解,nullptr == testInst会调用

bool operator==(T const* a, T const* b)

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

应该调用:

inline operator const T *() const 

其他问题:

所以这个问题归结为:

  • 为什么不比较运算符的默认实现将参数作为const引用,然后调用const函数?
  • 你能举一个c ++参考吗?

3 个答案:

答案 0 :(得分:5)

当const和非const存在重载时,如果您使用的对象是非const,编译器将始终调用非const版本。否则,什么时候会调用非const版本

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

const SharedPointer<int>& constRef = testInst;
eq = nullptr == constRef;

在Qt&#39; QSharedDataPointer的上下文中,您还可以在需要指针时显式使用constData函数。

对于QSharedDataPointer的预期用法,此行为通常不是问题。它应该是Facade类的成员,因此仅从其成员函数中使用。那些不需要修改(因此不需要分离)的成员函数本身应该是const,这使得成员对指针的访问权限在const上下文中。因此不会脱离。

编辑以回答编辑:

  

根据我的理解,nullptr == testInst会调用

bool operator==(T const* a, T const* b)

这种理解是不正确的。运营商的过载分辨率相当复杂,为参与分辨率的运营商的内置版本提供了大量代理签名。该过程在标准的[over.match.oper]和[over.built]中描述。

具体来说,相关内置的相等候选者在[over.built] p16和17中定义。这些规则说明每个指针类型T,{{1存在。现在,operator ==(T, T)int*都是指针类型,因此两个相关的签名是const int*operator ==(int*, int*)。 (还有operator ==(const int*, const int*),但它不会被选中。)

为了区分两个重载,编译器必须比较转换序列。对于第一个参数,operator ==(std::nullptr_t, std::nullptr_t)nullptr_t -> int*都是相同的;它们是指针转换。将nullptr_t -> const int*添加到其中一个指针中。 (参见[conv.ptr]。)对于第二个参数,转换分别为constSharedPointer<int> -> int*。第一个是用户定义的转换,调用SharedPointer<int> -> const int*,无需进一步转换。第二个是用户定义的转换,调用operator int*(),这需要首先进行资格转换才能调用operator const int*() const版本。因此,首选非const版本。

答案 1 :(得分:0)

这是因为解析表达式testInst == nullptr

  1. 让我们来看看类型:
    testInst的类型为SharedPointer<int> 根据用例,nullptrT*void*的类型(为简化起见)。
    所以表达式为SharedPointer<int> == int*
  2. 我们需要具有相同的类型来调用比较运算符。有两种可能性:
    1. 解析为int* == int*
      这包括拨打operator int *()operator int const *() const [引证需要]
    2. 解析为SharedPointer<int> == SharedPointer<int>
      这需要致电SharedPointer(nullptr)
  3. 因为第二个选项会创建一个新对象,而第一个选项没有,所以第一个选项是更好的匹配。 [引证需要]
  4. 现在解析bool operator==(int [const] *a, int [const] *b)之前([const]无关紧要),testInst必须转换为int*
    这涉及到对转化operator int *()operator int const *() const的调用 这里,将调用非const版本,因为testInst不是const。 [引证需要]
  5. 我创建了一个建议,在Qt Bugs中为T*添加QSharedDataPointer<T>的比较运算符:https://bugreports.qt.io/browse/QTBUG-66946

答案 2 :(得分:0)

也许这段代码可以让你了解会发生什么:

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

如果对const对象(或对它的引用)进行了演示,则选择const强制转换运算符(情况3和4),反之亦然。