使用 mutable 允许修改 unordered_set 中的对象
Using mutable to allow modification of object in unordered_set
请考虑以下内容code:
#include <iostream>
#include <unordered_set>
struct MyStruct
{
int x, y;
double mutable z;
MyStruct(int x, int y)
: x{ x }, y{ y }, z{ 0.0 }
{
}
};
struct MyStructHash
{
inline size_t operator()(MyStruct const &s) const
{
size_t ret = s.x;
ret *= 2654435761U;
return ret ^ s.y;
}
};
struct MyStructEqual
{
inline bool operator()(MyStruct const &s1, MyStruct const &s2) const
{
return s1.x == s2.x && s1.y == s2.y;
}
};
int main()
{
std::unordered_set<MyStruct, MyStructHash, MyStructEqual> set;
auto pair = set.emplace(100, 200);
if (pair.second)
pair.first->z = 300.0;
std::cout << set.begin()->z;
}
我正在使用 mutable
允许修改 MyStruct
的成员 z
。我想知道这是否可行且合法,因为该集合是 a) 无序的和 b) 我没有使用 z
进行散列或相等?
典型使用的可变变量是允许 const 方法更改不构成对象基本状态一部分的数据成员,例如从对象的非可变数据派生的惰性评估值。
声明 public 数据成员可变不是一个好习惯,即使该对象被标记为 const,您也允许在外部更改对象状态。
在您的示例代码中,您使用了 mutable,因为(根据您的评论),没有它您的代码将无法编译。您的代码无法编译,因为从 emplace 返回的迭代器是 const.
有两种不正确的方法来解决这个问题,一种是使用 mutable 关键字,另一种几乎同样糟糕的方法是将 const 引用强制转换为非常量引用。
emplace 方法旨在将对象直接构建到集合中,避免调用复制构造函数。这是一个有用的优化,但如果它会损害代码的可维护性,则不应使用它。您应该在构造函数中初始化 z,或者您不应该使用 emplace 将对象添加到集合中,而是设置 z 的值,然后将对象插入到集合中。
如果您的对象在构造后永远不需要更改,您应该通过将它们声明为 const 或将数据成员声明为私有并添加非可变访问器方法(这些应该声明为 const)来使您的 class/struct 不可变.
我想说这是对 "Mutable" 关键字的完美使用。
可变关键字用于标记不属于 class 的 "state" 的成员(即它们是某种形式的缓存或中间值,不代表逻辑状态对象)。
您的相等运算符(以及其他比较器(或序列化数据的任何函数)(或生成哈希的函数))定义对象的状态。您的相等比较器在检查对象的逻辑状态时不使用成员 'z',因此成员 'z' 不是 class 状态的一部分,因此使用"mutable" 存取器。
现在说。我确实认为以这种方式编写代码非常脆弱。 class 中没有任何内容可以阻止未来的维护者不小心使 z 成为 class 状态的一部分(即将其添加到哈希函数),从而打破在 std::unordered_set<>
。因此,您在使用它时应该非常明智,并花大量时间编写注释和单元测试以确保维持先决条件。
我还会研究“@Andriy Tylychko”关于将 class 分解为常量部分和值部分的评论,以便您可以将其用作 std::unordered_map
的一部分。
问题是 z
不是对象状态的一部分 仅 在特定类型 unordered_set
的上下文中。
如果继续这条路线,为了以防万一,最终会让一切都变得可变。
一般来说,你问的不是 possible,因为元素哈希需要在元素修改时自动重新计算。
most 一般你可以做的是有一个元素修改的协议,类似于 Boost.MultiIndex https://www.boost.org/doc/libs/1_68_0/libs/multi_index/doc/reference/ord_indices.html#modify 中的 modify
功能。
代码很丑陋,但由于 extract
的存在,它可以在重要时变得相当高效(好吧,你的特定结构仍然不会从移动中受益)。
template<class UnorderedSet, class It, class F>
void modify(UnorderedSet& s, It it, F f){
It h = it; ++h;
auto val = std::move(s.extract(it).value());
f(val);
s.emplace_hint(h, std::move(val) );
}
int main(){
std::unordered_set<MyStruct, MyStructHash, MyStructEqual> set;
auto pair = set.emplace(100, 200);
if (pair.second) modify(set, pair.first, [](auto&& e){e.z = 300;});
std::cout << set.begin()->z;
}
(代码未经测试)
@JoaquinMLopezMuños(Boost.MultiIndex 的作者)建议重新插入整个节点。我认为这会像这样工作:
template<class UnorderedSet, class It, class F>
void modify(UnorderedSet& s, It it, F f){
It h = it; ++h;
auto node = s.extract(it);
f(node.value());
s.insert(h, std::move(node));
}
EDIT2:最终测试代码,需要 C++17(用于提取)
#include <iostream>
#include <unordered_set>
struct MyStruct
{
int x, y;
double z;
MyStruct(int x, int y)
: x{ x }, y{ y }, z{ 0.0 }
{
}
};
struct MyStructHash
{
inline size_t operator()(MyStruct const &s) const
{
size_t ret = s.x;
ret *= 2654435761U;
return ret ^ s.y;
}
};
struct MyStructEqual
{
inline bool operator()(MyStruct const &s1, MyStruct const &s2) const
{
return s1.x == s2.x && s1.y == s2.y;
}
};
template<class UnorderedSet, class It, class F>
void modify(UnorderedSet& s, It it, F f){
auto node = s.extract(it++);
f(node.value());
s.insert(it, std::move(node));
}
int main(){
std::unordered_set<MyStruct, MyStructHash, MyStructEqual> set;
auto pair = set.emplace(100, 200);
if(pair.second) modify(set, pair.first, [](auto&& e){e.z = 300;});
std::cout << set.begin()->z;
}
请考虑以下内容code:
#include <iostream>
#include <unordered_set>
struct MyStruct
{
int x, y;
double mutable z;
MyStruct(int x, int y)
: x{ x }, y{ y }, z{ 0.0 }
{
}
};
struct MyStructHash
{
inline size_t operator()(MyStruct const &s) const
{
size_t ret = s.x;
ret *= 2654435761U;
return ret ^ s.y;
}
};
struct MyStructEqual
{
inline bool operator()(MyStruct const &s1, MyStruct const &s2) const
{
return s1.x == s2.x && s1.y == s2.y;
}
};
int main()
{
std::unordered_set<MyStruct, MyStructHash, MyStructEqual> set;
auto pair = set.emplace(100, 200);
if (pair.second)
pair.first->z = 300.0;
std::cout << set.begin()->z;
}
我正在使用 mutable
允许修改 MyStruct
的成员 z
。我想知道这是否可行且合法,因为该集合是 a) 无序的和 b) 我没有使用 z
进行散列或相等?
典型使用的可变变量是允许 const 方法更改不构成对象基本状态一部分的数据成员,例如从对象的非可变数据派生的惰性评估值。 声明 public 数据成员可变不是一个好习惯,即使该对象被标记为 const,您也允许在外部更改对象状态。
在您的示例代码中,您使用了 mutable,因为(根据您的评论),没有它您的代码将无法编译。您的代码无法编译,因为从 emplace 返回的迭代器是 const.
有两种不正确的方法来解决这个问题,一种是使用 mutable 关键字,另一种几乎同样糟糕的方法是将 const 引用强制转换为非常量引用。
emplace 方法旨在将对象直接构建到集合中,避免调用复制构造函数。这是一个有用的优化,但如果它会损害代码的可维护性,则不应使用它。您应该在构造函数中初始化 z,或者您不应该使用 emplace 将对象添加到集合中,而是设置 z 的值,然后将对象插入到集合中。
如果您的对象在构造后永远不需要更改,您应该通过将它们声明为 const 或将数据成员声明为私有并添加非可变访问器方法(这些应该声明为 const)来使您的 class/struct 不可变.
我想说这是对 "Mutable" 关键字的完美使用。
可变关键字用于标记不属于 class 的 "state" 的成员(即它们是某种形式的缓存或中间值,不代表逻辑状态对象)。
您的相等运算符(以及其他比较器(或序列化数据的任何函数)(或生成哈希的函数))定义对象的状态。您的相等比较器在检查对象的逻辑状态时不使用成员 'z',因此成员 'z' 不是 class 状态的一部分,因此使用"mutable" 存取器。
现在说。我确实认为以这种方式编写代码非常脆弱。 class 中没有任何内容可以阻止未来的维护者不小心使 z 成为 class 状态的一部分(即将其添加到哈希函数),从而打破在 std::unordered_set<>
。因此,您在使用它时应该非常明智,并花大量时间编写注释和单元测试以确保维持先决条件。
我还会研究“@Andriy Tylychko”关于将 class 分解为常量部分和值部分的评论,以便您可以将其用作 std::unordered_map
的一部分。
问题是 z
不是对象状态的一部分 仅 在特定类型 unordered_set
的上下文中。
如果继续这条路线,为了以防万一,最终会让一切都变得可变。
一般来说,你问的不是 possible,因为元素哈希需要在元素修改时自动重新计算。
most 一般你可以做的是有一个元素修改的协议,类似于 Boost.MultiIndex https://www.boost.org/doc/libs/1_68_0/libs/multi_index/doc/reference/ord_indices.html#modify 中的 modify
功能。
代码很丑陋,但由于 extract
的存在,它可以在重要时变得相当高效(好吧,你的特定结构仍然不会从移动中受益)。
template<class UnorderedSet, class It, class F>
void modify(UnorderedSet& s, It it, F f){
It h = it; ++h;
auto val = std::move(s.extract(it).value());
f(val);
s.emplace_hint(h, std::move(val) );
}
int main(){
std::unordered_set<MyStruct, MyStructHash, MyStructEqual> set;
auto pair = set.emplace(100, 200);
if (pair.second) modify(set, pair.first, [](auto&& e){e.z = 300;});
std::cout << set.begin()->z;
}
(代码未经测试)
@JoaquinMLopezMuños(Boost.MultiIndex 的作者)建议重新插入整个节点。我认为这会像这样工作:
template<class UnorderedSet, class It, class F>
void modify(UnorderedSet& s, It it, F f){
It h = it; ++h;
auto node = s.extract(it);
f(node.value());
s.insert(h, std::move(node));
}
EDIT2:最终测试代码,需要 C++17(用于提取)
#include <iostream>
#include <unordered_set>
struct MyStruct
{
int x, y;
double z;
MyStruct(int x, int y)
: x{ x }, y{ y }, z{ 0.0 }
{
}
};
struct MyStructHash
{
inline size_t operator()(MyStruct const &s) const
{
size_t ret = s.x;
ret *= 2654435761U;
return ret ^ s.y;
}
};
struct MyStructEqual
{
inline bool operator()(MyStruct const &s1, MyStruct const &s2) const
{
return s1.x == s2.x && s1.y == s2.y;
}
};
template<class UnorderedSet, class It, class F>
void modify(UnorderedSet& s, It it, F f){
auto node = s.extract(it++);
f(node.value());
s.insert(it, std::move(node));
}
int main(){
std::unordered_set<MyStruct, MyStructHash, MyStructEqual> set;
auto pair = set.emplace(100, 200);
if(pair.second) modify(set, pair.first, [](auto&& e){e.z = 300;});
std::cout << set.begin()->z;
}