Vin*_*ent 17 c++ templates move-semantics c++11
我已经阅读了关于C++ 11中移动语义的一些描述,我想知道它可以在什么上下文中使用.目前,许多C++数学库使用模板元编程来延迟评估.
如果M = A + B + C*D,其中M,A,B,C和D是矩阵,则模板元编程允许避免无用的副本.移动语义是一种更方便的方式来做这些事吗?
如果不是,则在什么上下文中使用移动语义.如果是,与模板元编程相比,这种用途有什么区别/限制?
How*_*ant 34
我相信对于你所谓的"模板元编程"更精确的术语是表达模板.
如果您的矩阵动态分配其数据,则移动语义可以帮助将数据从对象传输到对象(包括来自/来自临时对象)的表达式,例如:
M = A + B + C*D
Run Code Online (Sandbox Code Playgroud)
另一方面,表达模板将完全消除临时性.
如果你的矩阵没有动态分配它们的数据(例如,如果它们是固定的大小),那么移动语义根本不会对你的性能有所帮助.
将表达式模板应用于矩阵库将获得最高性能.这也是一种非常困难的实现技术.移动语义更容易实现,并且除了表达式模板之外还可以完成(如果有可以传输的内存等资源).
综上所述:
移动语义不会消除临时性,但会在临时数据库中传输动态分配的内存,而不是重新分配它.
表达模板消除了临时性.
Ste*_*sop 13
我不是这些优化的专家,但据我所知,你所讨论的延迟评估技术是通过在矩阵类型上定义算术运算符来实现的,例如A+B+C*D不返回矩阵,它返回一个代理对象,可以转换为矩阵.当它被分配时会发生这种情况M,并且转换代码将通过库设计者可以提出的最有效的方式计算结果矩阵的每个单元,从而避免临时矩阵对象.
所以,假设程序包含 M = A + B + C * D;
如果除了operator+以通常的方式使用之外你没有做任何巧妙的operator+=事情,你会得到类似的东西一旦正常,C++ 03风格的复制省略开始:
Matrix tmp1 = C;
tmp1 *= D;
Matrix tmp2 = A;
tmp2 += B;
tmp2 += tmp1;
M = tmp2;
Run Code Online (Sandbox Code Playgroud)
通过延迟评估,您可能会得到更多类似的内容:
for (int i = 0; i < M.rows; ++i) {
for (int j = 0; j < M.cols; ++j) {
/* not necessarily the best matrix multiplication, but serves to illustrate */
c_times_d = 0;
for (int k = 0; k < C.cols; ++k) {
c_times_d += C[i][k] * D[k][j];
}
M[i][j] = A[i][j] + B[i][j] + c_times_d;
}
}
Run Code Online (Sandbox Code Playgroud)
而"没什么聪明"的代码可以做几个单独的加法循环和更多的赋值.
据我所知,移动语义在这种情况下没有多大帮助.在你写的没有什么可以使我们摆脱A,B,C或者D,所以我们要以相当于落得:
Matrix tmp1 = C;
tmp1 *= D;
Matrix tmp2 = A;
tmp2 += B;
tmp2 += std::move(tmp1);
M = std::move(tmp2);
Run Code Online (Sandbox Code Playgroud)
所以移动语义对最后一点以外的任何东西没有帮助,其中运算符的rvalue版本可能比常规版本更好.如果你写的话std::move(A) + std::move(B) + std::move(C) * std::move(D),有更多的可用,因为我们不需要复制C或A,但我仍然认为结果不如延迟评估代码.
基本上,移动语义对延迟评估提供的优化的一些重要部分没有帮助:
1)延迟评估,中间结果永远不需要实际存在为完整矩阵.移动语义不会保存编译器A+B在某些时候在内存中创建完整的矩阵.
2)延迟评估,我们可以M在完成计算整个表达式之前开始修改.移动语义无助于编译器重新排序修改:即使编译器足够聪明以发现潜在的机会,如果存在抛出异常的任何危险,则必须按正确的顺序保持对非临时对象的修改,因为如果有的话A + B + C * D投掷的一部分,然后M必须保持开始.