为什么std::weak_ptr类没有operator<运算符?

9
我在使用弱指针作为map中的键。然而,当我尝试编译时,出现了难看的消息,我理解为缺少std::weak_ptr的比较运算符,这在std::map中是必需的,因为它根据键值对元素进行排序。
现在,weak_ptr类是一种智能指针类型类,因此可以使用指向某些受管理数据的指针。
这个类没有提供operator<方法的基本实现,是否有充分的理由呢?我的意思是,比较指针值对我来说似乎很明显,如果需要以不同的方式工作,则应该能够扩展或重新定义该方法,以获得预期的行为,不是吗?
非常感谢您的见解。

2
鉴于弱指针的基础对象可能随时被销毁,因此比较弱指针是完全没有意义的。在 < 完成指针比较的皮秒后,其中一个基础对象可能已经被销毁,因此您的比较结果也就无从谈起了。这与一般指针比较无关,正如答案所声称的那样。std::less 强制执行指针上的弱排序。查一下吧。比较普通指针没有问题。但由于其本质,弱指针比较是完全没有意义的。 - Sam Varshavchik
好的,谢谢。然而,据我所知,弱指针有一个指向匹配 shared_ptr 实例(家族中计算使用它的智能指针数量并保存“真实”指针的“持有者结构”的指针),对吧?因此,即使实例计数降至 0,使用原始地址进行比较也不应该太难 - 除非实现在指针“过期”时删除这个非常内部的结构(我不知道,我没有检查 STL 的内部代码。也许我应该)。 - Kzwix
3个回答

12

std::owner_less 是在 map 中将智能指针作为键排序的正确方法。

#include <map>
#include <memory>
#include <string>

struct Foo
{
};

using my_key = std::weak_ptr<Foo>;
using my_comp = std::owner_less<my_key>;

int main()
{
    auto m = std::map<my_key, std::string, my_comp>();

    auto p = std::make_shared<Foo>();
    auto p1 = std::make_shared<Foo>();

    m.emplace(p, "foo");
    m.emplace(p1, "bar");

    p.reset();
    p1.reset();
}

我不知道这个。发现得好 :) - rubenvb
我也不知道。但是,我很难理解指针在哪些情况下可能会“不同”。我看到的有关std::owner_less(或std::weak_ptr.owner_before)的文档说,它比较所拥有的指针,而不管get()的结果如何,并且指出这些指针可能不同“(例如,因为它们指向同一对象中的不同子对象)”那怎么可能呢?我的意思是,如果我有指向类Foo的智能指针,其中一个指针将如何指向“不同的子对象”? - Kzwix
@Kzwix,有一个shared_ptr的构造函数可以让你共享受控对象的生命周期,但是指向其中的子对象。请参见此页面中的注释8:http://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr - Richard Hodges
谢谢,Richard,你让我发现了另一件事。然而,文档说它期望一个指向“element_type”的指针。在参考文献中,“element_type”被定义为类型T本身(因为我们还没有进入C++17)。然而,构造函数的描述明确说明,只要保证该指针在共享指针存在的时间内“存活”,它就可以是我们想要的任何东西。因此,可以是字段的指针,或者是向下转换版本的指针,等等。但是,我想知道,如果我的类Foo有一个类型为Bar的字段,那么如果它期望Foo,我怎么提供Bar呢? - Kzwix

11

更新:有一种明确定义的方法可以比较std::weak_ptr,请参见@Richard's answer。我将保留我的回答作为历史档案。


实现一个好的operator<需要从weak_ptr创建一个shared_ptr并调用它的operator<。这是

  1. 一项“昂贵”的操作
  2. 当底层的shared_ptr不再存在时,结果未定义。

通常很难得到一个定义良好且性能良好的weak_ptr排序。

请注意,您总是可以向您的映射对象传递自定义比较器,您可以按照任何您想要的方式进行实现并将其限制在该对象中。但是,这取决于您找出一个好的方法来实现这一点。也许您可以使用连接的shared_ptr的控制块指针,但这是您无法访问的实现细节。因此,我真的看不到有任何有意义的实现方式。


指针值(而不是指向的值)进行比较并不会导致未定义行为,这是我认为OP想要做的。operator<没有被定义的实际原因是因为它可能会给出错误的结果,而不是因为它是未定义行为。(特别地,不存在的shared_ptr可以很容易地处理;这就是std::owner_less所做的。) - Konrad Rudolph
@KonradRudolph 在某些系统上可能是未定义的(并且在所有实现中都是实现定义的),请参见DR 1438 - Jonathan Wakely

1
我认为关键点在于(摘自C++ Concurrency in Action,第194页,重点强调)
使用类似hazard pointers的方式依赖于这样一个事实:在引用的对象被删除后,使用指针的值是安全的。如果您使用new和delete的默认实现,则这在技术上是未定义的行为,因此您需要确保您的实现允许它,或者您需要使用允许这种用法的自定义分配器。
提供operator<()需要读取原始指针值,这可能导致未定义行为(即当expired()返回true时)。除非您首先lock()并检查返回值不为空,否则几乎无法保证比较是正确的。

那本书的作者报告了相关措辞的缺陷,现在标准明确指出比较无效指针值是实现定义的,而不一定是未定义的。但这仍然会使weak_ptr定义比较成为一个问题。 - Jonathan Wakely

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