在今天重构一些代码以更改到std::unique_ptr的原始指针时,由于http://en.cppreference.com/w/cpp/language/eval_order错误,我遇到了一个分段错误。
旧代码执行了如下操作:
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)从变量boom中删除一个移出的变量!
可能的修复方法是在调用Foo::name之前获得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));
}这两种解决方案都需要额外的代码行,而且似乎不像对add的原始调用(尽管是UB)那样可组合。
问题:
发布于 2016-11-15 20:24:57
对我来说,这两个提议看起来很糟糕。在任何一种情况下,您都要通过移动将您的Foo对象分发出去。这意味着在那之后,你不能再对它的状态做出任何假设。在处理第一个参数(对字符串或指向对象的指针的引用)之前,可以在add函数中释放它。是的,它在当前的实现中可以工作,但是一旦任何人接触到add的实现或更深层次的实现,它就会崩溃。
安全的方法:
addadd方法,它只接受一个类型为Foo的参数,并在该方法中提取Foo::name,而不将其作为参数。但是,在add方法中仍然存在相同的问题。在第二种方法中,您应该能够绕过计算顺序问题,首先创建一个新的映射条目(带有默认值),然后获得一个可变的引用,然后分配值:
auto& entry = _foo_map[f->name];
entry = std::move(f);不确定您的map实现是否支持获取对条目的可变引用,但对许多人来说,它应该可以工作。
如果我再想一想的话,你也可以采用“复制名字”的方法。无论如何,地图键都需要复制它。如果您手动复制它,您可以移动它,因为键没有开销。
std::string name = f->name;
_foo_map[std::move(name)] = std::move(f);编辑:
正如注释中指出的那样,应该可以在_foo_map[f->name] = std::move(f)函数中直接分配add,因为这里保证了计算顺序。
发布于 2016-11-16 08:20:32
您可以让add引用f,从而避免不必要的复制/移动(相同的复制/移动会破坏f->name):
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));
}必须在调用foo_map[name]之前对operator=进行评估,因此即使name引用依赖于f的内容,也没有问题。
https://stackoverflow.com/questions/40618581
复制相似问题