C++17 表达式求值顺序和 std::move

标签 c++ c++17 operator-precedence

今天在重构一些代码以更改指向 std::unique_ptr 的原始指针时,我遇到了由于 order of evaluation 导致的段错误错误。

旧代码做了如下的事情:

void add(const std::string& name, Foo* f)
{
    _foo_map[name] = f;
}

void process(Foo* f)
{
    add(f->name, f);
}

第一次天真地重构代码以使用 std::unique_ptr:

void add(const std::string& name, std::unique_ptr<Foo> f)
{
    _foo_map[name] = std::move(f);
}

void process(std::unique_ptr<Foo> f)
{
    add(f->name, std::move(f)); // segmentation-fault on f->name
}

重构的代码会导致段错误,因为首先处理第二个参数 (std::move(f)),然后处理第一个参数 (f->name) 取消引用一个移动的变量,繁荣!

对此的可能解决方法是获取 Foo::name 的句柄 before 在调用 add:

void process(std::unique_ptr<Foo> f)
{
    const std::string& name = f->name;
    add(name, std::move(f));
}

或许:

void process(std::unique_ptr<Foo> f)
{
    Foo* fp = f.get();
    add(fp->name, std::move(f));
}

这两种解决方案都需要额外的代码行,而且看起来不像最初(尽管是 UB)对 add 的调用那样可组合。

问题:

最佳答案

对我来说,这 2 个提案看起来很糟糕。在其中任何一个中,您都将 Foo 对象随移动而放弃。这意味着你不能再对它的状态做出任何假设。在处理第一个参数(对字符串的引用或指向对象的指针)之前,它可以在 add 函数中被释放。是的,它可以在当前的实现中工作,但是一旦有人触及 add 的实现或其中更深层次的任何东西,它就会中断。

安全的方法:

  • 复制字符串并将其作为第一个参数传递给 add
  • 重构您的 add 方法,使其只接受 Foo 类型的单个参数,并在方法中提取 Foo::name 而不会不要把它当作论据。但是,您在 add 方法中仍然遇到同样的问题。

在第二种方法中,您应该能够通过首先创建一个新的映射条目(具有默认值)并获取对其的可变引用并随后分配值来解决评估顺序问题:

auto& entry = _foo_map[f->name];
entry = std::move(f);

不确定您的 map 实现是否支持获取对条目的可变引用,但对于许多人来说它应该可以工作。

如果我再考虑一下,您可能还会选择“复制名称”的方法。无论如何都需要为映射键复制它。如果您手动复制它,您可以移动它作为 key ,没有开销。

std::string name = f->name;
_foo_map[std::move(name)] = std::move(f);

编辑:

正如评论中指出的那样,应该可以在 add 函数中直接分配 _foo_map[f->name] = std::move(f) 因为此处保证评估顺序。

关于C++17 表达式求值顺序和 std::move,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40618581/

相关文章:

c++ - 将 dolfin::matrix 转换为 eigen::matrix

c++ - `enable_if` 与 `enum` 模板特化问题

c++ - QGraphicsItem 插入顺序

c++ - 当存在 float 精度问题时,如何实际解决凸包?

c++ - 在编译时以算法方式初始化 std::array

c++ - std::optional 是否更改函数的签名?

Java 优先级 - 转换和按位运算符

java - 为什么 != 在 if 语句的前面和在结尾处不一样?

language-design - 扩展Backus–Naur形式的操作顺序

C++ 游戏编程帮助。 super 简单