C++ 17表达式评估顺序和std :: move

Ste*_*mer 13 c++ operator-precedence c++17

虽然今天重构了一些代码以更改原始指针std::unique_ptr,但由于评估错误的顺序,我遇到了分段错误.

旧代码执行如下操作:

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

void process(Foo* f)
{
    add(f->name, f);
}
Run Code Online (Sandbox Code Playgroud)

第一个,天真的,重构代码使用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
}
Run Code Online (Sandbox Code Playgroud)

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

可能的解决方法是在调用Foo::name 之前获取句柄add:

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

也许:

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

这两种解决方案都需要额外的代码行,并且看起来几乎不像原始(尽管是UB)调用那样可组合add.

问题:

  • 2个提议的解决方案中的任何一个都是惯用的 C++,如果没有,是否有更好的选择?

  • 我发现由于P0145R3,C++ 17有变化 - 精炼C++的表达式评估顺序.这会改变上述任何解决方案/防止陷阱吗?

Mat*_*247 6

对我来说,这两个提案看起来很糟糕 在其中任何一个中,你都会Foo移动你的物体.这意味着在此之后你不能再对它的状态作出任何假设.add在处理第一个参数(对字符串或指向对象的指针)之前,可以在函数内部释放它.是的,它可以在当前的实现中工作,但是一旦任何人触及其中的add更深层次的实现或更深层的内容,它就会崩溃.

安全的方式:

  • 复制字符串并将其作为第一个参数传递给 add
  • 重构你的add方法,它只需要一个类型的参数FooFoo::name在方法中提取,而不是将它作为参数.但是,在add方法中仍然存在相同的问题.

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

auto& entry = _foo_map[f->name];
entry = std::move(f);
Run Code Online (Sandbox Code Playgroud)

不确定您的地图实现是否支持获取对条目的可变引用,但对于许多它应该有效.

如果我再考虑一下,你也可以选择"复制名称"的方法.无论如何都需要为地图键复制它.如果你手动复制它,你可以移动密钥,没有开销.

std::string name = f->name;
_foo_map[std::move(name)] = std::move(f);
Run Code Online (Sandbox Code Playgroud)

编辑:

正如评论中指出的那样,应该可以直接_foo_map[f->name] = std::move(f)add函数内部进行分配,因为这里保证了评估顺序.

  • 你仍然可以做`_foo_map [f-> name] = std :: move(f)`因为,即使`f`首先被转换为r值引用,它也不会被移动到有一些东西需要移入,这将是`_foo_map [f-> name]`的结果 (4认同)