复制map迭代器对值的省略

时间:2018-03-01 12:11:52

标签: c++ c++11 copy-elision

在下面的MVE中,Get函数的返回值是否有资格进行复制省略?

修改

我在某种程度上改变了这个例子。在Debug和Release版本中使用Visual Studio 2017,我在return语句中看到了一个复制结构。希望这只是因为我弄乱了Type,这有助于我进行调试。

#include <map>
#include <string>
#include <iostream>
#include <ostream>

struct Type
{
    Type()
    {
        std::cout << "Default construction\n";
    };

    explicit Type(std::string obj) : obj(std::move(obj))
    {
        std::cout << "Other construction\n";
    }
    ~Type() = default;

    Type(const Type& other) : obj{other.obj}
    {
        std::cout << "Copy construction\n";
    }

    Type(Type&& other) noexcept : obj{std::move(other.obj)}
    {
        std::cout << "Move constructor\n";
    }

    Type& operator=(const Type& other)
    {
        std::cout << "Copy assignment\n";
        if (this == &other)
            return *this;
        obj = other.obj;
        return *this;
    }

    Type& operator=(Type&& other) noexcept
    {
        std::cout << "Move assignment\n";
        if (this == &other)
            return *this;
        obj = std::move(other.obj);
        return *this;
    }

    friend std::ostream& operator<<(std::ostream& os, const Type& obj1)
    {
        return os << obj1.obj;
    }

    std::string obj;
};


std::map<std::string, Type> mVariables;

Type Get(const std::string& variableName)
{
    const auto variableIt = mVariables.find(variableName);
    if(variableIt==std::end(mVariables)) {
        throw std::runtime_error("Unknown variable requested.");
    }
    return variableIt->second;
}

int main()
{
    mVariables.emplace(std::make_pair("key", Type("value")));
    const auto value = Get("key");  
    std::cout << value;
    return 0;
}

上面的示例提供了以下输出,它提出了一些关于make_pair的问题,但这不是讨论。我想我的困惑是,在这个例子中是什么阻止了复制省略?

Other construction
Move constructor
Move constructor
Copy construction
value

2 个答案:

答案 0 :(得分:0)

我建议您在Compiler Explorer上播放。

我在GCC主干中看到的几乎所有的map和字符串函数都被内联,剩下的唯一函数调用是memcmp(用于内联查找和字符串比较)和{{1} }和new(对于返回的副本)。

clang trunk似乎没有内联。 memcpy仍然存在,但仍然只有一次致电map::findnew

答案 1 :(得分:0)

在C ++ 17 1 之前,const auto value = Get("key");的语义是从返回的表达式variableIt->second复制初始化临时对象,然后复制初始化{{1}来自临时对象。所以最初基本上有两个副本/动作。

根据N3797 [class.copy]第31段子弹3:

value直接构建value,可以省略临时对象的复制/移动。
  
      
  • 当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-nonqualified类型的类对象时,可以通过直接构造临时对象来省略复制/移动操作进入省略副本/移动的目标。
  •   

通过阻止临时对象在语义中实现,从C ++ 17开始保证此副本。 variableIt->second的新语义从const auto value = Get("key"); 2 变为初始化value

来自variableIt->second 的副本无法,因为它不符合variableIt->second声明中出现的复制省略要求,即N3797 [等级。复制]第31段子弹1 3

  
      
  • 在具有类返回类型的函数中的return语句中,当表达式为非易失性自动对象的名称时(函数或catch子句参数除外) )使用与函数返回类型相同的cv-unqualified类型,通过将自动对象直接构造为函数的返回值,可以省略复制/移动操作
  •   

这是合理的,因为return的生命周期未结束且可能在将来使用,因此variableIt->second无法作为value的别名进行优化,因此需要复制。

1 自C ++ 17以来唯一的区别是第三段中提到的保证副本省略。在我看来,从C ++ 17之前的语义开始分析是更为直接的。

2 在C ++ 17中有几个规则结合起来得出这个结论,对于这个问题并不重要,所以我从标准中发出相应规则的引用。

3 此规则的措辞在C ++ 17中有所改变,而规则基本相同。