运动语义是帮助 C++ 程序员解决自语言创建以来一直拖延的性能问题的工具,让我们看一个低效代码的示例:
std::string Quijote("En un lugar de La Mancha de cuyo nombre no quiero acordarme ...");
// Continua durante centenares de miles de caracteres -------------------------> ~~~
std::string TO_UPPER(std::string in)
{
std::transform(in.begin(), in.end(), in.begin(), std::toupper);
return in;
}
int main()
{
std::cout << TO_UPPER(Quijote);
return 0;
}
std::string Quijote("En un lugar de La Mancha de cuyo nombre no quiero acordarme ...");
// Continua durante centenares de miles de caracteres -------------------------> ~~~
// v------- referencia
std::string &TO_UPPER(std::string &in)
// referencia --------------------^
{
std::transform(in.begin(), in.end(), in.begin(), std::toupper);
return in;
}
int main()
{
std::cout << TO_UPPER(Quijote);
return 0;
}
使用引用可避免制作不必要的变量副本并提高代码性能。但引用并不能解决所有问题:
std::vector<std::string> &read_table(std::string table)
{
std::vector<std::string> result;
// fill result...
return result;
}
int main()
{
auto customers = read_table("customers");
return 0;
}
const float PI = 3.14159265359f;
float sqrPi = std::sqrt(PI);
auto t = std::time(nullptr);
auto f = t & 1? PI : sqrPi;
std::string hocus_pocus = "Hocus Pocus!";
auto lorem_ipsum = std::string("Lorem Ipsum!");
在 C++11 之前,您不能引用 Right Values,但是通过添加移动语义,这些值是可引用的,并且可以重载函数以接受它们:
std::string hocus_pocus = "Hocus Pocus!";
/* 1 */ void f(std::string &s); // Funcion que recibe una referencia normal.
/* 2 */ void f(std::string &&s); // Funcion que recibe una RvD.
f(hocus_pocus); // Llama a la primera sobrecarga.
f("Hocus Pocus!"); // Llama a la segunda sobrecarga.
f(std::string("Lorem Ipsum!"); // Llama a la segunda sobrecarga.
// Referencia a Valor Derecho ---vv
std::string TO_UPPER(std::string &&in)
{
std::transform(in.begin(), in.end(), in.begin(), std::toupper);
return std::move(in);
// ~~~~~~~~~~~~~~~~~~~~ <----- mover el valor fuera de la función.
}
std::string hocus_pocus = "Hocus Pocus!";
/* 1 */ void f(std::string &s); // Funcion que recibe una referencia normal.
/* 2 */ void f(std::string &&s); // Funcion que recibe una RvD.
f(hocus_pocus); // Llama a la primera sobrecarga.
f("Hocus Pocus!"); // Llama a la segunda sobrecarga.
f(std::string("Lorem Ipsum!")); // Llama a la segunda sobrecarga.
f(std::move(hocus_pocus)); // Llama a la SEGUNDA sobrecarga.
问题。
运动语义是帮助 C++ 程序员解决自语言创建以来一直拖延的性能问题的工具,让我们看一个低效代码的示例:
TO_UPPER
当您调用将变量复制Quijote
到函数的局部变量时in
,会处理此副本,然后返回处理后的副本并再次复制。在 C++ 中移动语义之前,我们可以使用引用来解决上述问题:
使用引用可避免制作不必要的变量副本并提高代码性能。但引用并不能解决所有问题:
在上面的代码中,我们有一个悬空引用,因为
result
函数的内部变量read_table
在函数退出的那一刻不再存在,因此在函数外部使用它会导致错误......所以我们回到方便的引用:解决方案。
想象一下,我们可以移动这些数据,而不是将数据从一个函数复制到另一个函数,这就是移动的语义。
移动的语义由“引用右值”(RvD)支持,它允许引用临时值,这在 C++11 之前是不可能的。
右值引用使用声明符
&&
(在声明上下文中,它对应于表达式中的“右值引用”,它是逻辑AND运算符)。但是什么是RvD?,右值是能够位于表达式右侧的所有内容,通常是临时值,其右侧的所有以下表达式
=
都是临时右值:在 C++11 之前,您不能引用 Right Values,但是通过添加移动语义,这些值是可引用的,并且可以重载函数以接受它们:
对于处理资源的对象,当它们接收到一个临时值(我们知道在评估表达式后将被删除的值)时,我们可以决定从临时交换资源以避免更昂贵的操作:
自由运动语义!
标准库从 C++11 调整其所有构造以利用移动语义,因此切换到 C++11 或更高版本的编译器可以在不更改代码的情况下为我们提供这些语义。这通常意味着仅通过重新编译就可以提高性能!
所以现在我们可以解决之前遇到的问题:
由于
std::string
C++11模板中加入了移动语义,所以当我们TO_UPPER
用临时值调用函数时,值会被移动,节省不必要的副本。该函数std::move
将一个值转换为 RvD,因此回到重载函数示例:重要的是要注意
std::move
,尽管收到名称move,但它不会移动任何东西,它只会将变量转换为 RvD,因此该指令不会在任何地方移动任何东西:我移动的东西会发生什么?
正如我们所看到的,它
std::move
从引用(&
)转换为RvD(&&
),实际上是接收到所述RvD的指令或构造函数执行移动操作,但是移动的对象呢?根据C++ 标准,移动的对象是有效的,即使它们的内容是不确定的(我的翻译和强调):
并非所有闪闪发光的都是金子。
由于几乎每次添加新功能时都会出现错误使用,因此滥用
std::move
它并不方便,因为它会阻止编译器应用优化导致性能恶化,例如以下代码有问题:根据C++ 标准(我的翻译和突出显示):
所以最好不要滥用调用
std::move
,因为它们避免了这些被称为“返回值优化”(OvR)(英文为 RVO)的编译器优化。值得注意的是,OvR 存在于 C++11 之前的 C++ 中,并且不需要程序员采取任何行动来利用它。更多信息。
您可以在此视频中看到对运动语义的解释(西班牙语视频,英文文本)。