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

时间:2016-11-15 19:56:01

标签: 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之前获取句柄{/ 1}} 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 个答案:

答案 0 :(得分:6)

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

安全的方式:

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

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

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

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

如果我再想一想,你也可以选择“复制名称”的方法。无论如何都需要为地图键复制它。如果你手动复制它,你可以移动它以获得密钥,没有开销。

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

修改

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

答案 1 :(得分:1)

您可以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的内容,也没有问题。