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++的表达式评估顺序.这会改变上述任何解决方案/防止陷阱吗?
对我来说,这两个提案看起来很糟糕 在其中任何一个中,你都会Foo移动你的物体.这意味着在此之后你不能再对它的状态作出任何假设.add在处理第一个参数(对字符串或指向对象的指针)之前,可以在函数内部释放它.是的,它可以在当前的实现中工作,但是一旦任何人触及其中的add更深层次的实现或更深层的内容,它就会崩溃.
安全的方式:
addadd方法,它只需要一个类型的参数Foo并Foo::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函数内部进行分配,因为这里保证了评估顺序.