没有标准的方法来比较智能指针和常规指针?

23
为什么C++标准不包括比较智能指针(unique_ptr,shared_ptr等)和常规指针(T*)的比较运算符?
汤姆
更新: 我不是想知道如何实现,而是想知道为什么它没有作为C++标准的一部分来定义?对于unique_ptr和shared_ptr这样的定义将是微不足道的。
以下是一个使用案例: 类A具有使用unique_ptr键的映射。 unique_ptr用于内存管理。 当类A的用户传递一个常规指针时,会在此映射中执行查找。不幸的是,标准没有定义比较运算符。

2
智能指针和原始指针不是同一回事。为什么要拿苹果和梨子比较呢? - Tony The Lion
2
@TonyTheLion 要找出哪个更好吃,当然是苹果啦。(提示:如果是非常好的苹果,那就算了梨) :) - jalf
1
我会比较这些苹果和梨的原始指针值。 - Tom Deseyn
1
@MasterT 找到了:https://dev59.com/v2Mm5IYBdhLWcg3wIsM2 这是关于 std::unordered_set 的,但大多数解决方案应该都适用。哦,而且似乎即使是 C++14 也不会放宽关联容器的接口限制。 - Christian Rau
如果在std::set<unique_ptr<T>>中查找T*有效,那么您也会期望反过来:在std::set<T*>中查找std::unique_ptr<T>同样有效。这些事情往往比您想象的更加复杂。 - MSalters
显示剩余3条评论
4个回答

17
为什么C++标准不包括比较智能指针(unique_ptr、shared_ptr等)和普通指针(T*)的比较运算符?
这个决定背后的原则通常是“使你的接口易于正确使用,难以或不可能被错误使用”。
从概念上讲,智能指针和裸指针并不相同。
智能指针强制实施限制(例如,“unique_ptr是一个指针,但您不能有多个副本”)。尽管它们的行为类似于指针(在某种程度上),但它们具有不同的语义。
也就是说,如果您有:
int *p1 = new int{5};
std::unique_ptr<int> p2{new int{5}};

p1 == p2.get();

比较很容易做到,显式地表明了你正在比较同类对象(很容易理解正在发生的事情 - 你正在使用原始指针的值进行比较)。
另一方面,拥有自定义的比较运算符会引起奇怪的问题(“unique_ptr是唯一的;你怎么能将其与其他东西进行比较呢? - 如果它是唯一的,它应该总是不同的”)。

1
如果它是独特的,那么它应该总是不同的。auto& rp0 = p2; auto& rp1 = p2; p1 == p2(想象函数参数)。请注意,对于std::unique_ptr和其他unique_ptrnullptr_t,都有比较运算符。前者实际上比较所拥有的指针,即p2 == p2 :<=> p2.get() == p2.get(),因此比较p1 == p2.get()将是明确定义的,而且不会出乎意料。 - dyp
“将被良好定义且不会令人惊异” - 这是语言设计者以不同方式讨论的一个老问题(即“对象相等 vs. 对象具有等值”); 在Java中,这是字符串比较问题的根源(您使用s1.compare(s2)而不是s1 == s2来比较字符串)。如果您使用std::unique_ptr<T>T*创建操作员==,则会_强制_所有代码客户端无法将它们在概念上视为不同(因为它们具有不同的类型),除非他们编写丑陋/容易出错的代码(例如首先比较类型)。 - utnapistim
我的意思是:两个unique_ptroperator==已经仅基于所拥有的指针定义。因此,与原始指针的比较不需要有不同的行为。 - dyp
我建议,如果您可以执行 p1 == p2,那么这意味着 p2 正在被隐式转换为 int*(或者我想 p1 可能会被隐式转换为智能指针,但在其他方面这是很危险的),这将使从智能指针中提取未受控制的原始指针变得太容易了。 - Andre Kostur
我不太同意这个观点——unique_ptr 就是一个指针。如果不是的话,你可以这样做:template <typename T> bool operator==(const unique_ptr<T>& x, const unique_ptr<T>& y) { return (!x && !y) || (&x == &y); }然后你就可以在常数时间内查找向量中的 unique_ptrs。在我看来,unique_ptr 只是带有标记的原始指针。 - Denis Yaroshevskiy

10
你可以直接使用 smart_ptr.get() == raw_ptr, 这样有什么问题吗?

2
我知道那样做可行。为什么标准不允许直接比较呢?例如,考虑在具有智能指针键类型的映射中查找。您需要定义自己的比较器才能使其正常工作。 - Tom Deseyn
这让我更加清晰。阅读起来更容易,因为你不需要那么多的信息。无论如何,添加自己的operator==作为自由函数不应该是一个问题。 - Tobias Langner
3
@MasterT 这个问题问错了。它为什么应该呢? - R. Martinho Fernandes
2
@R. Martinho Fernandes 没有错误的问题,只有不完整的答案。 - Alex Pana
3
假设您有一个 vector<unique_ptr<T>> 并且您想要找到特定的 T*。目前,您必须使用带有 lambda 谓词的 std::find_if 来解包和比较 unique_ptr。通过提议的 operator== 重载,一个更易读的 std::find 将变得可能。 - pasbi

3
由于比较运算符只比较指针本身,而非指向的内容。由于智能指针“拥有”实际的原始指针,任何其他原始指针都不能与被比较的智能指针相同。

1
原始指针可以/应该由程序中不需要保持对象存活的部分使用。在比较时,您是否关心谁保持对象存活? - Tom Deseyn
1
@MasterT 当然需要。否则为什么要使用智能指针呢?当然,不同的操作需要不同的相等含义。但是通常的含义一定要考虑所有权(以及可能的删除器)。 - Konrad Rudolph
2
@KonradRudolph 为什么在比较智能指针时应考虑所有权和删除器?所有权是关于何时删除,而删除器是关于如何删除。在我看来,删除和比较之间没有语义关系。 - Tom Deseyn

-2
主要原因可能是,如果您使用的是标准智能指针,就不应该有指向对象的原始指针。拥有一个指向对象的原始指针几乎可以保证,在维护过程中的某个时刻,会有人从中创建第二个智能指针,结果会是灾难性的。(唯一的例外是空指针,并且标准允许与空指针常量进行比较。)

1
如果程序的某个部分不需要了解对象的生命周期,我倾向于在类接口中使用常规指针。请参阅https://dev59.com/Y2sz5IYBdhLWcg3wy7A9 - Tom Deseyn
8
强调任何原始指针都是非所有权的,这应该是使用 C++11 时对它们的默认假设。 - Xeo
通常情况下,没有拥有指针的情况,也就不需要使用任何智能指针。指针拥有任何东西的概念有点奇怪;指针是非常低级别的对象,没有真正的行为或责任。大多数应用程序都不需要使用任何智能指针。另一方面,混合使用std::shared_ptr和裸指针会导致灾难。如果您正在使用std::shared_ptr,那么您不应该有一个指向该对象的裸指针(当然,这意味着没有成员函数,因为this是一个裸指针)。 - James Kanze
@MasterT 任何拥有指向对象的指针的人都需要被告知该对象的生命周期何时结束。但通常通过观察者模式来解决,而不需要使用智能指针。 - James Kanze
非所有权引用应该是原始指针。最终,某人拥有指针,并在该位置处理内存管理,例如使用智能指针。智能指针中存在排序操作的主要原因是将它们放入像集合或映射这样的容器中。当面对原始指针(例如观察者模式)时,您希望将该原始指针与您拥有的指针进行比较,这就是我在此提出此问题的原因。 - Tom Deseyn
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接