我这里有两段代码给你看。它们是两个类,每个类都提供一个Move Constructor和一个返回临时函数的函数。
我很困惑:在这两种情况下,我都定义了一个Move Constructor和一个返回临时的随机成员函数。但是行为发生了变化,我的问题是为什么。
请注意,在以下示例中,运算符<<重载是为了打印列表(在第一种情况下)和双数据成员(在第二种情况下)。
移动构造词得到了
template<typename T>
class GList
{
public:
GList() : il{ nullptr } {}
GList(const T& val) : il{ new Link<T>{ val,nullptr } } {}
GList(const GList<T>& copy) {}
GList(GList<T>&& move)
{
std::cout << "[List] Move constructor called" << std::endl;
// ... code ...
}
// HERE IS THE FUNCTION WHICH RETURNS A TEMPORARY!
GList<T> Reverse()
{
GList<T> result;
if (result.il == nullptr)
return *this;
...
...
...
return result;
}
};
int main()
{
GList<int> mylist(1);
mylist.push_head(0);
cout << mylist.Reverse();
return 0;
}
输出结果为:
[List]移动名为
的构造函数0
1
执行COPY ELISION
class Notemplate
{
double d;
public:
Notemplate(double val)
{
d = val;
}
Notemplate(Notemplate&& move)
{
cout << "Move Constructor" << endl;
}
Notemplate(const Notemplate& copy)
{
cout << "Copy" << endl;
}
Notemplate Redouble()
{
Notemplate example{ d*2 };
return example;
}
};
int main()
{
Notemplate my{3.14};
cout << my.Redouble();
return 0;
}
输出结果为:
6.28
我希望在第二个例子中调用Move Constructor。 毕竟,函数的逻辑是相同的:返回一个临时的。
有人会解释为什么没有发生这种情况吗?
我如何处理复制品?
我希望我的代码是最便携的,我怎样才能确定编译器的这些优化?
答案 0 :(得分:9)
在another SO answer的评论中,OP澄清了他在这里的要求:
我听说即使超过1,也会发生复制 退货声明。我想知道禁止复制的时间
所以我试图在这里解决这个问题:
在以下情况下,允许删除复制/移动操作(C ++标准称为复制省略):
在具有类返回类型的函数的return
语句中,当表达式是具有自动存储持续时间的非易失性对象的名称时(函数除外)参数或由处理程序的 exception-declaration 引入的变量)与函数返回类型相同的类型(忽略cv-qualification),通过构造自动对象可以省略复制/移动操作直接进入函数的返回值。
在 throw-expression 中,当操作数是非易失性自动对象的名称(函数或catch子句参数除外),其范围不超出在最里面的 try-block 的末尾(如果有的话),通过将自动对象直接构造到异常对象中,可以省略从操作数到异常对象的复制/移动操作。
当一个未绑定到引用的临时类对象被复制/移动到具有相同类型的类对象(忽略cv-qualification)时,可以通过构造它来省略复制/移动操作临时对象直接进入省略的复制/移动目标。
当异常处理程序的 exception-declaration 声明一个相同类型的对象(cv-qualification除外)作为异常对象时,可以通过处理复制操作来省略复制操作如果除了为exception-declaration声明的对象执行构造函数和析构函数之外,程序的含义将保持不变,则将 exception-declaration 作为异常对象的别名。不能从异常对象移动,因为它始终是左值。
在所有其他情况下禁止复制省略。
函数中的返回语句数对复制省略的合法性没有任何影响。但是,允许编译器不执行复制省略,即使它是合法的,出于任何原因,包括返回语句的数量。
C ++ 17更新
现在有一些地方需要复制省略。如果prvalue可以直接绑定到by-value函数参数,或者按值返回类型,或者命名为局部变量,则在C ++ 17中必须使用copy elision。这意味着编译器甚至不会打扰检查副本或移动构造函数。 Legal C ++ 17:
struct X
{
X() = default;
X(const X&) = delete;
X& operator=(const X&) = delete;
};
X
foo(X)
{
return X{};
}
int
main()
{
X x = foo(X{});
}
答案 1 :(得分:0)
复制省略是一种优化,现在,每个现代编译器都提供。
在C ++中返回大型类对象时,这种技术适用于...... ,但不适用于所有情况!
在第一个示例中,编译器执行Move Constructor,因为我们在函数中有多个return语句。