我注意到std::string
(真的std::basic_string
是)移动赋值运算符是 noexcept
.这对我来说很有意义.但后来我发现,没有一个标准集装箱(例如std::vector
,std::deque
,std::list
,std::map
)宣布其移动赋值操作符noexcept
.这对我来说没什么意义.std::vector
例如,A 通常实现为三个指针,并且指针当然可以移动分配而不会抛出异常.然后我想也许问题是移动容器的分配器,但std::string
也有分配器,所以如果这是问题,我希望它会影响std::string
.
那么为什么std::string
移动赋值运算符noexcept
,但标准容器的移动赋值运算符不是?
How*_*ant 26
我相信我们正在考虑标准缺陷.noexcept
如果将规范应用于移动赋值运算符,则该规范有些复杂.无论我们是在谈论basic_string
还是在谈论,我都相信这种说法是正确的vector
.
根据[container.requirements.general]/p7我的容器移动赋值运算符应该做的英文翻译是:
C& operator=(C&& c)
Run Code Online (Sandbox Code Playgroud)
如果
alloc_traits::propagate_on_container_move_assignment::value
是true
,则转储资源,移动分配分配器,并从中传输资源c
.如果
alloc_traits::propagate_on_container_move_assignment::value
是false
和get_allocator() == c.get_allocator()
,则转储资源,并从中传输资源c
.如果
alloc_traits::propagate_on_container_move_assignment::value
是false
和get_allocator() != c.get_allocator()
,此举给每个c[i]
.
笔记:
alloc_traits
是指allocator_traits<allocator_type>
.
当alloc_traits::propagate_on_container_move_assignment::value
被true
移动赋值运算符可以指定noexcept
,因为所有的它将会是解除分配现有资源,然后从源窃取资源.同样在这种情况下,分配器也必须移动分配,并且移动分配必须是noexcept
容器的移动分配noexcept
.
如果alloc_traits::propagate_on_container_move_assignment::value
是false
,如果这两个分配器是相等的,那么它会做同样的事情,#2.但是,在运行时之前,人们不知道分配器是否相等,因此您不能noexcept
基于这种可能性.
当alloc_traits::propagate_on_container_move_assignment::value
是false
,如果这两个分配器是不相等,那么一个具有以移动分配每个单独的元件.这可能涉及向目标添加容量或节点,因此本质上noexcept(false)
.
总结如下:
C& operator=(C&& c)
noexcept(
alloc_traits::propagate_on_container_move_assignment::value &&
is_nothrow_move_assignable<allocator_type>::value);
Run Code Online (Sandbox Code Playgroud)
我认为C::value_type
在上述规范中没有任何依赖性,因此我认为它应该同样适用于std::basic_string
尽管C++ 11另有说明.
更新
在下面的评论中,Columbo正确地指出事情一直在变化.我上面的评论与C++ 11有关.
对于草案C++ 17(此时看起来似乎很稳定),情况有所改变:
如果alloc_traits::propagate_on_container_move_assignment::value
是true
,那么规范现在需要移动分配allocator_type
to not throw exception(17.6.3.5 [allocator.requirements]/p4).所以不再需要检查is_nothrow_move_assignable<allocator_type>::value
.
alloc_traits::is_always_equal
已被添加.如果这是真的,则可以在编译时确定上面的点3不能抛出,因为可以传输资源.
所以noexcept
容器的新规范可能是:
C& operator=(C&& c)
noexcept(
alloc_traits::propagate_on_container_move_assignment{} ||
alloc_traits::is_always_equal{});
Run Code Online (Sandbox Code Playgroud)
并且,对于std::allocator<T>
,alloc_traits::propagate_on_container_move_assignment{}
并且alloc_traits::is_always_equal{}
都是真的.
现在,在C++ 17草案中,两者vector
和string
移动赋值都完全符合此noexcept
规范.然而,其他容器带有本noexcept
说明书的变体.
如果你关心这个问题,最安全的做法是测试你关心的容器的显式特化.我已经container<T>
为VS,libstdc ++和libc ++完成了这个:
http://howardhinnant.github.io/container_summary.html
这项调查大约有一年的历史,但据我所知仍然有效.
我认为这样做的理由是这样的.
basic_string
仅适用于非数组POD类型.因此,他们的破坏者必须是微不足道的.这意味着如果你swap
进行移动分配,那么移动到的字符串的原始内容尚未被销毁对你来说并不重要.
而容器(basic_string
技术上不是C++规范的容器)可以包含任意类型.具有析构函数的类型,或包含具有析构函数的对象的类型.这意味着,它在准确保持控制对用户更重要的当一个对象被销毁.它明确指出:
的所有现有的元件一个 [被移动到对象]被上移分配到或销毁.
所以差异确实有意义.noexcept
一旦开始释放内存(通过分配器),就无法进行移动分配,因为这可能会因异常而失败.因此,一旦你开始要求在move-assign上释放内存,你就放弃了能够强制执行noexcept
.
归档时间: |
|
查看次数: |
3697 次 |
最近记录: |