如果一个过期的weak_ptr会给出未定义的行为,那么owner_less有什么意义呢
What is the point of owner_less if an expired weak_ptr will give undefined behavior?
请考虑到我的经验不足,但我不理解std::owner_less
的要点。
我已经看到,不建议使用以weak_ptr
为密钥的map
,因为过期的weak_ptr
密钥会破坏映射,实际上:
如果它过期,那么容器的顺序就被破坏了,之后尝试使用容器将产生未定义的行为。
这种行为有多不明确?我问这个问题的原因是因为文档中提到了owner_less
:
此函数对象提供基于所有者(而不是基于值)的std::weak_ptr和std::shared_ptr的混合类型排序。顺序是这样的,只有当两个智能指针都为空或都管理同一对象时,即使通过get()获得的原始指针的值不同(例如,因为它们指向同一对象内的不同子对象)
同样,这是我的经验不足,但听起来map
不会被过期的weak_ptr
:完全破坏
返回weak_ptr对象是空的,还是它所属的所有者组中没有更多的shared_ptr。
过期指针在锁定时充当空的weak_ptr对象,因此不能再用于恢复拥有的shared_ptr。
听起来它可能会变得更加松弛,而不是完全不确定。如果一个实现删除了过期的weak_ptr,并且根本没有或根本没有使用任何延迟的weak_Ptr,那么行为何时会变得未定义?
如果一个人的实现不考虑顺序,只需要一种方便的方式来将weak_ptr
与数据关联,那么行为是否仍然未定义?换句话说,find
是否会开始返回错误的密钥?
地图
我在文档中能找到的唯一问题是上面提到的,过期的weak_ptr将返回等效的。
根据这些文档,对于不依赖订购也不使用过期weak_ptr
s:的实现来说,这不是问题
关联
关联容器中的元素由它们的键引用,而不是由它们在容器中的绝对位置引用。
已订购
容器中的元素始终遵循严格的顺序。所有插入的元素都按此顺序给定一个位置。
地图
每个元素都将一个键与映射值相关联:键用于标识其主要内容为映射值的元素。
这听起来像是,如果一个实现不关心顺序,也不使用过期的weak_ptr
s,那么就没有问题,因为值是由键而不是按顺序引用的,所以find
ing一个过期的weak_ptr
可能会返回另一个weak_ptr
s值,但由于在这个特定的实现中除了erase
d之外没有其他用途,所以没有问题。
我可以看到,无论是什么应用程序,使用weak_ptr
排序或过期的weak_ptr
都可能是一个问题,但所有行为似乎都远未定义,因此map
或set
似乎不会被过期的CCD20完全破坏。
map
、weak_ptr
和owner_less
是否有更多的技术解释反驳了这些文献和我的解释?
澄清一点。使用owner_less时,过期的weak_ptr不是UB。来自标准
在运算符()定义的等价关系下!运算符()(a,b) &!operator()(b,a),两个shared_ptr或weak_ptr实例等价于当且仅当它们共享所有权或都为空时。
需要记住的一件事是,一个空的weak_ptr是从未被分配过有效的shared_ptr的,或者是被分配了一个空shared_ptr/weak_prt的。已过期的weak_ptr不是空的weak_ptr。
编辑:
上面的定义取决于"空"weak_ptr意味着什么。那么,让我们看看标准
constexpr weak_ptr()noexcept;
效果:构造一个空的weak_ptr对象
后置条件:use_count()==0。- weak_ptr(const weak_ptr&r)noexcept
- 模板weak_ptr(const weak_ptr&r)noexcept
模板weak_ptr(const shared_ptr&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。这是因为在共享对象的所有弱指针也被释放之前,底层引用计数不能被释放。
下面是一个演示相同问题的最小示例:
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
}
CCD_ 27和CCD_ 28要求它们的密钥比较器在它们的密钥类型上实现严格的弱排序关系。这意味着,除其他外,如果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
随后将由于其为空而与其他CCD_34进行不同的比较,并且不能再保证严格的弱排序关系。在这种情况下,修复方法是一样的:使用一个自定义比较器,它不受共享状态变化的影响;CCD_ 35就是这样一个比较器。
这种行为有多不明确?
未定义是未定义的。没有连续体。
如果一个人的实现[…],行为何时会变得不明确?
只要包含的元素不再具有定义明确的严格弱序关系。
如果一个人的实现[…]行为仍然没有定义?换句话说,
find
会不会开始返回错误的密钥?
未定义的行为不仅限于返回错误的密钥。它可以做任何事情。
这听起来像是[…]没有问题,因为值是按键引用的,而不是按顺序引用的。
如果没有排序,键就缺乏引用值的内在能力。
std::sort
也需要订购。CCD_ 38在其上可能是有用的。
在map
或set
中,不那么重要——将weak_ptr
作为其中一个的关键就是追求未定义的行为。由于您无论如何都必须手动同步容器和指针的生存期,因此您还可以使用原始指针(或手动滚动的非拥有智能指针,以某种方式处理过期问题)来使其更清晰。
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 如果我std::dynamic_pointer_cast并且底层dynamic_cast的结果为null,那么返回的sh
- 如果"new int"返回"int*",那么为什么"new int[n]"不返回"int**"?
- 如果我已经有一个头,那么模板(-t)文件属于哪里
- 如果 KEY 是 std::list 或 std::vector 而不是值,那么 std::map 的默认行为是什么?
- C++ 警告:将新创建的 gsl::owner<> 分配给非所有者
- C++ 运算符修改/元编程策略,用于不那么冗长的语法
- 如果 C 函数仍然可以间接执行(通过回调函数),那么将它声明为静态函数是否是一种不好的做法?
- 而不是那么多的 if 语句,我想要一个逻辑,我可以用一个语句或优化的方式来完成
- 在C++中,如果"int a = 3; int* p = &a;",那么为什么不允许"const int* &pp = p",但允许"const int* const &pp = p"?
- 如果偶数浮点数(分数数)的模数为 2,那么它不会给出'0'结果!在C++
- C++我们可以取消引用此指针吗?如果是这样,那么如何,如果不是,那为什么?
- 共享内存中的健壮互斥锁不是那么健壮
- C++类析构函数删除成员(如果"owner"?
- 如果由不同的线程写入 8 字节,那么现代英特尔 x86 上的 8 字节读取是否保证理智?
- 我可以使用常量定义数组的长度,那么为什么 int d[b] 不起作用呢?
- 如果在命令行c++中给出了一个文件,那么如何读取该文件
- 在c++中,如果首先禁止默认构造,那么禁止复制构造有意义吗
- 如果我的容器位于两个现有值之间,那么伪造迭代器类别是否合理
- 如果我的类正确地管理了一个资源,那么拥有智能的poointer有什么意义