这就是为什么C++ 11中的移动构造函数有意义吗?

Mar*_*sen 8 c++ c++11

我最近在与Bjarne Stoustrup的一次演讲中谈到了c ++ 11以及为什么它有意义.

他令人敬畏的一个例子是移动构造者的新闻'&&'符号.

然后我想回家,开始思考,"我什么时候需要这样的东西?"

我的第一个例子是下面的代码:

class Number {
private:
    int value;
public:
    Number(const int value) : value(value){
        cout << "Build Constructor on " << value << endl;
    }
    Number(const Number& orig) : value(orig.value){
        cout << "Copy Constructor on " << value << endl;        
    }
    virtual ~Number(){}

    int toInt() const{
        return value;
    }


    friend const Number operator+(const Number& n0, const Number& n1); 
};

const Number operator+(const Number& n0, const Number& n1){
    return  Number(n0.value + n1.value);
}

int main(int argc, char** argv) {

    const Number n3 = (Number(2) + Number(1));
    cout << n3.toInt() << endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这段代码正是移动构造函数应该解决的问题.n3变量是从对'+'运算符返回的值的引用构造的.

除了这是运行代码的输出:

Build Constructor on 1
Build Constructor on 2
Build Constructor on 3
3

RUN SUCCESSFUL 
Run Code Online (Sandbox Code Playgroud)

输出显示的是复制构造函数永远不会被调用 - 这是关闭优化的.我很难扭转代码的一部分,足以让它运行副本construtor.将结果包装在std :: pair中就可以了,但这让我想到了.

运算符算术中的move-constructors的参数实际上是一个失败的参数吗?

为什么我的拷贝构造函数被调用,为什么它被调用:

using namespace std;

class Number {
private:
    int value;
public:
    Number(const int value) : value(value){
        cout << "Build Constructor on " << value << endl;
    }
    Number(const Number& orig) : value(orig.value){
        cout << "Copy Constructor on " << value << endl;        
    }
    virtual ~Number(){}

    int toInt() const{
        return value;
    }


    friend const std::pair<const Number, const Number> operator+(const Number& n0, const Number& n1); 
};

const std::pair<const Number, const Number> operator+(const Number& n0, const Number& n1){
    return  make_pair(Number(n0.value + n1.value), n0);
}

int main(int argc, char** argv) {

    const Number n3 = (Number(2) + Number(1)).first;
    cout << n3.toInt() << endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

随着输出:

Build Constructor on 1
Build Constructor on 2
Copy Constructor on 2
Build Constructor on 3
Copy Constructor on 3
Copy Constructor on 2
Copy Constructor on 3
Copy Constructor on 2
Copy Constructor on 3
3

RUN SUCCESSFUL 
Run Code Online (Sandbox Code Playgroud)

我想知道逻辑是什么以及为什么这对操作员基本上搞砸了性能?

更新:

我做了另一个修改,发现如果我make_pair用该对的实际模板化构造函数替换,pair<const Number, const Number>这减少了复制构造函数被触发的次数:

class Number {
private:
    int value;
public:
    Number(const int value) : value(value){
        cout << "Build Constructor on " << value << endl;
    }
    Number(const Number& orig) : value(orig.value){
        cout << "Copy Constructor on " << value << endl;        
    }
    virtual ~Number(){}

    int toInt() const{
        return value;
    }


    friend const std::pair<const Number, const Number> operator+(const Number& n0, const Number& n1); 
};



const std::pair<const Number, const Number> operator+(const Number& n0, const Number& n1){
    return  std::pair<const Number, const Number>(Number(n0.value + n1.value), n0);
}

int main(int argc, char** argv) {

    const Number n3 = (Number(2) + Number(1)).first;
    cout << n3.toInt() << endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

Build Constructor on 1
Build Constructor on 2
Build Constructor on 3
Copy Constructor on 3
Copy Constructor on 2
Copy Constructor on 3
3

RUN SUCCESSFUL
Run Code Online (Sandbox Code Playgroud)

所以它的使用make_pair是有害的吗?

Nic*_*las 14

考虑一下这个简单的C++代码:

class StringHolder
{
  std::string member;
public:

  StringHolder(const std::string &newMember) : member(newMember) {}
};

std::string value = "I am a string that will probably be heap-allocated.";
StringHolder hold(value);
Run Code Online (Sandbox Code Playgroud)

执行第二行后,存在多少个字符串副本?答案是两个:一个存储value,一个存储hold.那很好......有时候.通常有时候你想给某人一个字符串的副本,同时为自己保留.但有些时候你不想这样做.例如:

StringHolder hold("I am a string that will probably be heap-allocated.");
Run Code Online (Sandbox Code Playgroud)

这将创建一个std::string临时的,然后将其传递给StringHolder构造函数.构造函数将复制构造其成员.构造函数完成后,临时文件将被销毁.有一次,我们有两个字符串副本,无论如何.

没有必要有两个字符串副本.我们想要做的是移动std::string参数为StringHolder,使有永远只能一个字符串的副本.

这就是搬家建设的地方.

A std::string基本上只是指向已分配的字符数组的指针的包装器,以及包含该数组长度的大小(以及容量,但现在不用担心).如果你有一个std::string,并且你想移动到另一个,那么新字符串必须声明所分配的字符数组的所有权,旧字符串必须放弃所有权.在C++ 03中,您可以通过swap操作执行此操作:

std::string oldStr = "I am a string that will probably be heap-allocated.";
std::string newStr;
std::swap(newStr, oldStr);
Run Code Online (Sandbox Code Playgroud)

这将内容移动oldStrnewStr没有任何内存分配.

C++ 11的移动语法提供了两个不重要的特性std::swap.

首先,移动可以隐式发生(但只有在这样做时才安全).swap如果要交换,必须明确调用; 通过编写自然代码可以实现移动.例如,StringHolder从以前开始,做一个改变:

class StringHolder
{
  std::string member;
public:

  StringHolder(std::string newMember) : member(std::move(newMember)) {}
};

StringHolder hold("I am a string that will probably be heap-allocated.");
Run Code Online (Sandbox Code Playgroud)

创建了多少个这个字符串的副本?答案是......只有一个:临时的建设.因为它是一个临时的,C++ 11足够聪明,知道它可以移动构造由它初始化的任何东西.因此它移动构造构造函数的value参数StringHolder(或者更可能完全构造构造).这会将存储的内存从临时移动到newMember.所以不会发生复制.

之后,我们在构造时显式调用move构造函数member.这再次将分配的内存从移动newMembermember.

我们只有不断分配一个字符串一次.这可以大大节省性能.

现在,这与您自己类型的构造函数有什么关系?好吧,考虑一下这段代码:

class StringHolder
{
  std::string member;
public:

  StringHolder(std::string newMember) : member(std::move(newMember)) {}

  StringHolder(const StringHolder &old) : member(old.member) {}
  StringHolder(StringHolder &&old) : member(std::move(old.member)) {}
};

StringHolder oldHold = std::string("I am a string that will probably be heap-allocated.");
StringHolder newHold(oldHold);
Run Code Online (Sandbox Code Playgroud)

这一次,我们现在有一个带有复制和移动构造函数的类.我们得到多少份字符串?

二.当然是两个.我们有,oldHold并且newHold每个都有一个字符串的副本.

但是,如果我们这样做:

StringHolder oldHold = std::string("I am a string that will probably be heap-allocated.");
StringHolder newHold(std::move(oldHold));
Run Code Online (Sandbox Code Playgroud)

然后再次只有一个字符串的副本.

这就是为什么运动很重要.这就是重要的原因:它减少了你可能需要躺着的东西的副本数量.


为什么我的拷贝构造函数没有被调用

您的复制构造函数未被调用,因为它被省略了.它正在进行返回值优化.关闭优化并没有帮助,因为大多数编译器无论如何都会畏缩.没有理由不在何时可以进行省略.

对于函数返回值,在无法进行省略的情况下,移动很重要.

  • 他的论点不是无效的,你的例子太简单了,它没有分配,只有一个临时表达式,很容易被忽略.矩阵示例有两个临时表达式,因此并非所有副本都可以省略.必须计算(B*C)的结果,然后必须计算将其添加到Y的结果,然后将其复制到A并且可以省略最后一个副本,但不能是B*C的结果.如果您正在使用GCC,可以使用`-fno-elide-constructors`来复制省略 (4认同)