2014-10-07 44 views
0

在阅读有关move constructor从目前standard,我可以看到以下这一点:了解默认移动构造函数定义

12.8复制和移动类对象

如果一个类的定义X没有明确声明一个移动构造函数,只有当且仅当

- X没有用户声明的复制构造函数,
- X没有用户声明的复制赋值运算符,
- X没有用户声明的移动赋值运算符,并且
- X没有用户声明的析构函数。

[注意:当移动构造函数未被隐式声明或显式提供时,否则将调用移动构造函数的表达式可能会调用复制构造函数。 - 结束注意]

我觉得注意部分清楚地提到,默认移动构造函数的后退将是复制构造函数。为了理解这个概念,我编写了一个小程序来理解这个概念。

#include<iostream> 
struct handleclass { 
public: 
    handleclass():length{0}, p{nullptr} {} 
    handleclass(size_t l):length{l},p{new int[length]} { } 
    ~handleclass() { delete[] p; } 
    size_t length; 
    int* p; 
}; 

handleclass function(void) { 
    handleclass x(10); 
    return x; 
} 

int main() { 
    handleclass y; 
    std::cout<<y.length<<std::endl; 
    y = function(); 
    std::cout<<y.length<<std::endl; 

    handleclass a; 
    handleclass b(10); 
    a = std::move(b); 

    return 0; 
} 

显然这个方案是不正确,将由两个对象有不确定的操作(终止)由于资源的浅拷贝。但我的重点是了解程序中生成和使用的默认移动构造函数。我希望这个例子有意义。

在上面的程序中,在移动构造函数应该被调用的两种情况下,在我看来,编译器正在使用默认的拷贝构造函数。

根据标准中提到的上述规则,我认为我们应该得到编译器错误,因为现在程序显式地尝试调用移动构造函数,并且用户既没有执行也没有编译器生成默认(隐式),如上面的规则不满足?。

但是,这是得到编译没有任何警告/错误,并成功运行。有人可以解释有关默认(隐式)移动构造函数的概念吗?或者我错过了什么?

回答

1

您忘记了copy elision这意味着y = function();可能实际上不会调用任何副本或移动构造函数;只是x的构造函数和赋值运算符。

有些编译器可以让你禁用copy elision,正如该线程中提到的那样。

我不确定你的意思是“在应该调用移动构造函数的两种情况下”。实际上,在调用移动构造函数(您的对象没有移动构造函数)的情况下,以及可以调用复制构造函数的一种情况(return语句)可能不存在,但可能会被忽略。

你有两种情况赋值运算符y = function();a = std::move(b);。同样,因为你的班级没有移动赋值操作符,所以这些操作符将使用拷贝赋值操作符。

如果您从复制构造函数和移动构造函数中将代码添加到cout的对象,它可能会有助于您的测试。

+0

-fno-elide-constructors选项可用于禁用g ++的复制elision – 2014-10-07 02:01:16

1

事实上,由于用户声明的析构函数,没有隐式移动构造函数。

但有一个隐式拷贝构造函数和复制赋值操作符;由于历史原因,析构函数不会抑制这些,尽管这种行为已被废弃,因为(正如您指出的那样)它通常会给出无效的复制语义。

它们可以用来复制两个左值右值,因此被用于函数返回(可能被省略)和任务在您的测试程序。如果你想防止这种情况,那么你将不得不删除这些功能。

1

移动构造函数和移动赋值运算符未被隐式声明,因为您已明确定义了析构函数。尽管已经隐式声明了拷贝构造函数和拷贝赋值操作符(尽管此行为已被废弃)。

如果移动构造函数和移动赋值操作符未被隐式(或显式)声明,它将回退到使用拷贝等效项。

当您尝试调用移动分配时,它将回退到使用复制分配。