请考虑到我的经验不足,但我不明白std::owner_less
的要点。
我一直shown建议map
以weak_ptr
作为密钥,因为过期的weak_ptr
密钥会破坏地图,实际上是:
如果它到期,那么容器的订单就会被破坏,之后尝试使用容器会产生不确定的行为。
这种行为有多未定义?我问的原因是docs说owner_less
:
此函数对象提供std :: weak_ptr和std :: shared_ptr的基于所有者(而不是基于值)的混合类型排序。顺序是两个智能指针只有当它们都是空的或它们都管理同一个对象时才比较等价,即使get()获得的原始指针的值不同(例如因为它们指向不同的子对象)同一个对象)
同样,这是我缺乏经验的谈话,但听起来map
不会被expired weak_ptr
彻底打破:
返回weak_ptr对象是否为空或者它所属的所有者组中是否没有更多shared_ptr。
过期的指针在锁定时充当空的weak_ptr对象,因此无法再用于恢复拥有的shared_ptr。
听起来它可能比完全未定义更加松弛。如果一个人的实现删除了过期的weak_ptrs并且根本没有或没有使用任何延迟的,那么这个行为何时变得不确定?
如果一个人的实现不考虑顺序,但只需要一种方便的方法将weak_ptr
与数据相关联,那么行为是否仍未定义?换句话说,find
会开始返回错误的密钥吗?
我可以在文档中找到的唯一问题是上面引用的内容,即过期的weak_ptrs将返回等效内容。
根据这些docs,对于不依赖于排序的实现而言,这不是问题,也不是用于过期weak_ptr
的实现:
关联
关联容器中的元素由其键引用,而不是它们在容器中的绝对位置。
序
容器中的元素始终遵循严格的顺序。所有插入的元素都按此顺序给出一个位置。
地图
每个元素将键与映射值相关联:键用于标识主要内容为映射值的元素。
听起来如果一个实现不涉及顺序也不会使用过期的weak_ptr
,那么就没有问题,因为值是按键而不是按顺序引用的,所以find
过期的weak_ptr
{1}}可能会返回另一个weak_ptr
的值,但由于除了erase
d之外在此特定实现中没有用,因此没有问题。
我可以看到如何使用weak_ptr
排序或过期的weak_ptr
可能是一个问题,无论应用程序是什么,但所有行为似乎都未定义,因此{{1} }或map
似乎没有被过期的set
完全打破。
是否有更多针对weak_ptr
,map
和weak_ptr
的技术说明反驳这些文档和我的解释?
答案 0 :(得分:3)
这是一个演示同样问题的最小例子:
struct Character
{
char ch;
};
bool globalCaseSensitive = true;
bool operator< (const Character& l, const Character& r)
{
if (globalCaseSensitive)
return l.ch < r.ch;
else
return std::tolower(l.ch) < std::tolower(r.ch);
}
int main()
{
std::set<Character> set = { {'a'}, {'B'} };
globalCaseSensitive = false; // change set ordering => undefined behaviour
}
map
和set
要求其关键比较器在其键类型上实现严格弱排序关系。这意味着,除其他事项外,如果x
小于y
,那么x
总是小于y
。如果程序不能保证,程序会显示未定义的行为。
我们可以通过提供一个忽略区分大小写开关的自定义比较器来修复这个例子:
struct Compare
{
bool operator() (const Character& l, const Character& r)
{
return l.ch < r.ch;
}
};
int main()
{
std::set<Character, Compare> set = { {'a'}, {'B'} };
globalCaseSensitive = false; // set ordering is unaffected => safe
}
如果weak_ptr
到期,那么weak_ptr
随后会因其为空而与其他人进行不同的比较,并且无法再保证严格的弱排序关系。在这种情况下,修复是相同的:使用自定义比较器,该比较器不受共享状态的变化的影响; owner_less
就是这样一个比较器。
这种行为有多未定义?
未定义未定义。没有连续统一体。
如果某人的实施[...]何时行为未定义?
一旦所包含的元素不再具有明确定义的严格弱有序关系。
如果某人的实施仍然未定义?换句话说,
find
会开始返回错误的密钥吗?
未定义的行为不仅限于返回错误的密钥。它可以做任何。
这听起来像[...]没有问题,因为值是按键而不是按顺序引用的。
没有排序,密钥缺乏引用值的内在能力。
答案 1 :(得分:3)
澄清一点。使用owner_less时,过期的weak_ptr不是UB。从标准
在operator(),!operator()定义的等价关系下(a, b)&amp;&amp; !operator()(b,a),两个shared_ptr或weak_ptr实例 等同于当且仅当他们共享所有权或者都是空的时候。
要记住的一件事是,一个空的weak_ptr是一个从未被分配过有效的shared_ptr的文件,或者是一个已被分配了空的shared_ptr / weak_ptr的文件。已过期的weak_ptr不是空的weak_ptr。
编辑:
上面的定义取决于有一个&#34;空的&#34; weak_ptr的。所以,让我们看看标准
constexpr weak_ptr()noexcept;
效果:构造一个空的weak_ptr对象 后置条件:use_count()== 0.
- weak_ptr(const weak_ptr&amp; r)noexcept;
- 模板weak_ptr(const weak_ptr&amp; r)noexcept;
模板weak_ptr(const shared_ptr&amp; r)noexcept;
要求:除非Y *可隐式转换为T *,否则第二个和第三个构造函数不应参与重载决策。
效果:如果r为空,则构造为空 weak_ptr对象;否则,构造一个共享的weak_ptr对象 r的所有权并存储r。
中存储的指针的副本后置条件:use_count()== r.use_count()。
交换只是交换内容,赋值被定义为上面的构造函数和交换。
要创建一个空的weak_ptr
,您可以使用默认构造函数,或者传递一个空的weak_ptr或shared_ptr。现在,您注意到期并不会导致weak_ptr变空。它只会使use_count()
为零,expired()
返回true。这是因为在共享对象的所有弱指针也被释放之前,不能释放底层引用计数。
答案 2 :(得分:0)
std::sort
也需要订购。 owner_less
就可以了。
在map
或set
不那么这样做 - 将weak_ptr
作为关键是要求未定义的行为。正如您将要手动同步容器和指针的生命周期一样,您也可以使用原始指针(或手动滚动的非拥有智能指针以某种方式处理到期问题)来使其更清晰。