比较运算符中的共享指针常量 ==
Shared pointer constness in comparison operator ==
我偶然发现了我正在使用的共享指针的意外行为。
共享指针实现引用计数和分离(例如制作副本),如果需要的话,包含在非常量用法中的实例。
为此,对于每个 getter 函数,智能指针都有一个 const
和一个 non-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 的 QSharedDataPointer
持有一个 QSharedData
派生的实例,但这个问题不是特定于 Qt 的。
编辑:
据我了解,nullptr == testInst
会调用
bool operator==(T const* a, T const* b)
(因为我为什么要比较非常量指针?)
哪个应该调用:
inline operator const T *() const
更多问题:
- 为什么不默认使用 const 运算符?
- 这是因为无法单独通过return值的类型来选择功能吗?
=> 这在 Calling a const function rather than its non-const version 中得到了回答
所以这个问题归结为:
- 为什么比较运算符的默认实现不将参数作为常量引用然后调用常量函数?
- 您能否引用 c++ 参考资料?
当 const 和 non-const 存在重载时,如果您使用的对象是 non-const,编译器将始终调用 non-const 版本。否则,什么时候调用 non-const 版本 ever?
如果您想明确使用 const 版本,请通过 const 引用调用它们:
const SharedPointer<int>& constRef = testInst;
eq = nullptr == constRef;
在 Qt 的 QSharedDataPointer
上下文中,您还可以在需要指针时显式使用 constData
函数。
对于 QSharedDataPointer
的预期用途,此行为通常不是问题。它意味着成为外观 class 的成员,因此只能从其成员函数中使用。那些不需要修改(因此不需要分离)的成员函数本身应该是 const
,使成员对指针的访问处于 const
上下文中,因此不会分离。
编辑以回答编辑:
In my understanding, nullptr == testInst would invoke
bool operator==(T const* a, T const* b)
这种理解是不正确的。运算符的重载解析相当复杂,参与解析的运算符的 built-in 版本有大量代理签名。这个过程在标准的 [over.match.oper] 和 [over.built] 中有描述。
具体来说,相关的 built-in 相等候选项在 [over.built]p16 和 17 中定义。这些规则表明对于 每个 指针类型 T
,存在 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*
都是相同的;它们是指针转换。将 const
添加到其中一个指针中。 (参见 [conv.ptr]。)对于第二个参数,转换分别为 SharedPointer<int> -> int*
和 SharedPointer<int> -> const int*
。其中第一个是 user-defined 转换,调用 operator int*()
,无需进一步转换。第二个是 user-defined 转换,调用 operator const int*() const
,这需要先进行资格转换才能调用 const
版本。因此,首选non-const版本。
这是因为表达式 testInst == nullptr
的解析方式:
- 让我们看看类型:
testInst
属于 SharedPointer<int>
.
类型
nullptr
是(为了简化)类型 T*
或 void*
,具体取决于用例。
所以表达式显示为 SharedPointer<int> == int*
.
- 我们需要有相同的类型来调用比较运算符。有两种可能性:
- 解析为
int* == int*
。
这涉及调用 operator int *()
或 operator int const *() const
.
[需要引用]
- 解析为
SharedPointer<int> == SharedPointer<int>
这涉及调用 SharedPointer(nullptr)
.
- 因为第二个选项会创建一个新对象,而第一个选项不会,所以第一个选项更匹配。 [需要引用]
- 现在在解析
bool operator==(int [const] *a, int [const] *b)
之前([const]
是无关紧要的),testInst
必须转换为int*
。
这涉及调用转换 operator int *()
或 operator int const *() const
.
在这里,non-const 版本 将被调用,因为 testInst
不是 const。 [需要引用]
我在 Qt Bugs 上创建了一个建议,将 T*
的比较运算符添加到 QSharedDataPointer<T>
:https://bugreports.qt.io/browse/QTBUG-66946
也许这段代码可以让您了解发生了什么:
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),反之亦然。
我偶然发现了我正在使用的共享指针的意外行为。
共享指针实现引用计数和分离(例如制作副本),如果需要的话,包含在非常量用法中的实例。
为此,对于每个 getter 函数,智能指针都有一个 const
和一个 non-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 的 QSharedDataPointer
持有一个 QSharedData
派生的实例,但这个问题不是特定于 Qt 的。
编辑:
据我了解,nullptr == testInst
会调用
bool operator==(T const* a, T const* b)
(因为我为什么要比较非常量指针?)
哪个应该调用:
inline operator const T *() const
更多问题:
- 为什么不默认使用 const 运算符?
- 这是因为无法单独通过return值的类型来选择功能吗?
=> 这在 Calling a const function rather than its non-const version 中得到了回答
- 这是因为无法单独通过return值的类型来选择功能吗?
所以这个问题归结为:
- 为什么比较运算符的默认实现不将参数作为常量引用然后调用常量函数?
- 您能否引用 c++ 参考资料?
当 const 和 non-const 存在重载时,如果您使用的对象是 non-const,编译器将始终调用 non-const 版本。否则,什么时候调用 non-const 版本 ever?
如果您想明确使用 const 版本,请通过 const 引用调用它们:
const SharedPointer<int>& constRef = testInst;
eq = nullptr == constRef;
在 Qt 的 QSharedDataPointer
上下文中,您还可以在需要指针时显式使用 constData
函数。
对于 QSharedDataPointer
的预期用途,此行为通常不是问题。它意味着成为外观 class 的成员,因此只能从其成员函数中使用。那些不需要修改(因此不需要分离)的成员函数本身应该是 const
,使成员对指针的访问处于 const
上下文中,因此不会分离。
编辑以回答编辑:
In my understanding, nullptr == testInst would invoke
bool operator==(T const* a, T const* b)
这种理解是不正确的。运算符的重载解析相当复杂,参与解析的运算符的 built-in 版本有大量代理签名。这个过程在标准的 [over.match.oper] 和 [over.built] 中有描述。
具体来说,相关的 built-in 相等候选项在 [over.built]p16 和 17 中定义。这些规则表明对于 每个 指针类型 T
,存在 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*
都是相同的;它们是指针转换。将 const
添加到其中一个指针中。 (参见 [conv.ptr]。)对于第二个参数,转换分别为 SharedPointer<int> -> int*
和 SharedPointer<int> -> const int*
。其中第一个是 user-defined 转换,调用 operator int*()
,无需进一步转换。第二个是 user-defined 转换,调用 operator const int*() const
,这需要先进行资格转换才能调用 const
版本。因此,首选non-const版本。
这是因为表达式 testInst == nullptr
的解析方式:
- 让我们看看类型:
testInst
属于SharedPointer<int>
.
类型nullptr
是(为了简化)类型T*
或void*
,具体取决于用例。
所以表达式显示为SharedPointer<int> == int*
. - 我们需要有相同的类型来调用比较运算符。有两种可能性:
- 解析为
int* == int*
。
这涉及调用operator int *()
或operator int const *() const
.
[需要引用] - 解析为
SharedPointer<int> == SharedPointer<int>
这涉及调用SharedPointer(nullptr)
.
- 解析为
- 因为第二个选项会创建一个新对象,而第一个选项不会,所以第一个选项更匹配。 [需要引用]
- 现在在解析
bool operator==(int [const] *a, int [const] *b)
之前([const]
是无关紧要的),testInst
必须转换为int*
。
这涉及调用转换operator int *()
或operator int const *() const
.
在这里,non-const 版本 将被调用,因为testInst
不是 const。 [需要引用]
我在 Qt Bugs 上创建了一个建议,将 T*
的比较运算符添加到 QSharedDataPointer<T>
:https://bugreports.qt.io/browse/QTBUG-66946
也许这段代码可以让您了解发生了什么:
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),反之亦然。