C++ 标准是否保证插入关联容器失败不会修改右值引用参数?
Does the C++ standard guarantee that a failed insertion into an associative container will not modify the rvalue-reference argument?
#include <set>
#include <string>
#include <cassert>
using namespace std::literals;
int main()
{
auto coll = std::set{ "hello"s };
auto s = "hello"s;
coll.insert(std::move(s));
assert("hello"s == s); // Always OK?
}
C++ 标准是否保证插入关联容器失败不会修改右值引用参数?
没有
虽然@NathanOliver指出当且仅当没有等效键时才不会插入元素,但并不保证参数不会被修改。
其实[map.modifiers]说的是下面
template <class P>
pair<iterator, bool> insert(P&& x);
equivalent to return emplace(std::forward<P>(x)).
其中 emplace
可以完美地转发参数以构建另一个 P
,使 x
处于某个有效但不确定的状态。
这是一个示例,它也演示(不是证明)使用 std::map
(关联容器),值会移动一点:
#include <iostream>
#include <utility>
#include <string>
#include <map>
struct my_class
{
my_class() = default;
my_class(my_class&& other)
{
std::cout << "move constructing my_class\n";
val = other.val;
}
my_class(const my_class& other)
{
std::cout << "copy constructing my_class\n";
val = other.val;
}
my_class& operator=(const my_class& other)
{
std::cout << "copy assigning my_class\n";
val = other.val;
return *this;
}
my_class& operator=(my_class& other)
{
std::cout << "move assigning my_class\n";
val = other.val;
return *this;
}
bool operator<(const my_class& other) const
{
return val < other.val;
}
int val = 0;
};
int main()
{
std::map<my_class, int> my_map;
my_class a;
my_map[a] = 1;
std::pair<my_class, int> b = std::make_pair(my_class{}, 2);
my_map.insert(std::move(b)); // will print that the move ctor was called
}
明确且明确否。标准没有这种保证,这就是 try_emplace 存在的原因。
见注释:
Unlike insert or emplace, these functions do not move from rvalue
arguments if the insertion does not happen, which makes it easy to
manipulate maps whose values are move-only types, such as
std::map<std::string, std::unique_ptr<foo>>
. In addition, try_emplace
treats the key and the arguments to the mapped_type separately, unlike
emplace
, which requires the arguments to construct a value_type
(that
is, a std::pair
)
(仅针对 C++17 的答案)
我认为正确答案介于 NathanOliver(现已删除)的答案和 AndyG 的答案之间。
正如 AndyG 指出的那样,这样的保证 cannot 通常存在:有时,库 必须 实际上执行移动构造只是为了确定插入是否可以发生。 emplace
函数就是这种情况,其行为由标准指定为:
Effects: Inserts a value_type
object t
constructed with std::forward<Args>(args)...
if and only if there is no element in the container with key equivalent to the key of t
.
我们可以将其解释为对象 t
无论如何都会被构造,然后如果因为值 t
或 t.first
已经无法插入而被丢弃分别存在于集合或映射中。由于 std::map
的方法 template <class P> pair<iterator, bool> insert(P&&)
是根据 emplace
指定的,正如 AndyG 指出的那样,它具有相同的行为。正如 SergeyA 指出的那样,try_emplace
方法旨在避免此问题。
然而,在OP给出的具体例子中,插入的值与容器的值类型完全相同。这种 insert
调用的行为由 NathanOliver 先前给出的一般要求段落指定:
Effects: Inserts t
if and only if there is no element in the container with key equivalent to the key of t
.
在这种情况下,没有许可库在插入未发生的情况下修改参数。我相信除了标准明确允许的之外,调用库函数不应该有任何可观察到的副作用。因此,这种情况下,t
一定不能修改。
#include <set>
#include <string>
#include <cassert>
using namespace std::literals;
int main()
{
auto coll = std::set{ "hello"s };
auto s = "hello"s;
coll.insert(std::move(s));
assert("hello"s == s); // Always OK?
}
C++ 标准是否保证插入关联容器失败不会修改右值引用参数?
没有
虽然@NathanOliver指出当且仅当没有等效键时才不会插入元素,但并不保证参数不会被修改。
其实[map.modifiers]说的是下面
template <class P>
pair<iterator, bool> insert(P&& x);
equivalent to
return emplace(std::forward<P>(x)).
其中 emplace
可以完美地转发参数以构建另一个 P
,使 x
处于某个有效但不确定的状态。
这是一个示例,它也演示(不是证明)使用 std::map
(关联容器),值会移动一点:
#include <iostream>
#include <utility>
#include <string>
#include <map>
struct my_class
{
my_class() = default;
my_class(my_class&& other)
{
std::cout << "move constructing my_class\n";
val = other.val;
}
my_class(const my_class& other)
{
std::cout << "copy constructing my_class\n";
val = other.val;
}
my_class& operator=(const my_class& other)
{
std::cout << "copy assigning my_class\n";
val = other.val;
return *this;
}
my_class& operator=(my_class& other)
{
std::cout << "move assigning my_class\n";
val = other.val;
return *this;
}
bool operator<(const my_class& other) const
{
return val < other.val;
}
int val = 0;
};
int main()
{
std::map<my_class, int> my_map;
my_class a;
my_map[a] = 1;
std::pair<my_class, int> b = std::make_pair(my_class{}, 2);
my_map.insert(std::move(b)); // will print that the move ctor was called
}
明确且明确否。标准没有这种保证,这就是 try_emplace 存在的原因。
见注释:
Unlike insert or emplace, these functions do not move from rvalue arguments if the insertion does not happen, which makes it easy to manipulate maps whose values are move-only types, such as
std::map<std::string, std::unique_ptr<foo>>
. In addition,try_emplace
treats the key and the arguments to the mapped_type separately, unlikeemplace
, which requires the arguments to construct avalue_type
(that is, astd::pair
)
(仅针对 C++17 的答案)
我认为正确答案介于 NathanOliver(现已删除)的答案和 AndyG 的答案之间。
正如 AndyG 指出的那样,这样的保证 cannot 通常存在:有时,库 必须 实际上执行移动构造只是为了确定插入是否可以发生。 emplace
函数就是这种情况,其行为由标准指定为:
Effects: Inserts a
value_type
objectt
constructed withstd::forward<Args>(args)...
if and only if there is no element in the container with key equivalent to the key oft
.
我们可以将其解释为对象 t
无论如何都会被构造,然后如果因为值 t
或 t.first
已经无法插入而被丢弃分别存在于集合或映射中。由于 std::map
的方法 template <class P> pair<iterator, bool> insert(P&&)
是根据 emplace
指定的,正如 AndyG 指出的那样,它具有相同的行为。正如 SergeyA 指出的那样,try_emplace
方法旨在避免此问题。
然而,在OP给出的具体例子中,插入的值与容器的值类型完全相同。这种 insert
调用的行为由 NathanOliver 先前给出的一般要求段落指定:
Effects: Inserts
t
if and only if there is no element in the container with key equivalent to the key oft
.
在这种情况下,没有许可库在插入未发生的情况下修改参数。我相信除了标准明确允许的之外,调用库函数不应该有任何可观察到的副作用。因此,这种情况下,t
一定不能修改。