前言
在C++的学习过程中,我们经常会采用引用来作为传递的参数,可以减少拷贝次数。然而在大型的数据操作中,我们很容易造成拷贝,而且这些拷贝会涉及大量的数据复制,从而降低程序的主体性能。今天我们尝试以一个案例来介绍如何采用右值引用来减少数据的复制。
案例
我们在矩阵的常规实现中,一般会采用以下模式, 如test.cc所示:
// test.cc
class Matrix{
public:
Matrix(int a, int b):col(b),raw(a){
std::cout << "Matrix(int &a, int &b):col(b),raw(a)" << std::endl;
data = new int*[raw];
for (int i = 0; i < raw; ++i) {
data[i] = new int[col];
for (int j = 0; j < col; j++) {
data[i][j] = i+j;
}
}
}
Matrix(const Matix & m){
std::cout << "Matrix(Matrix & m)" << std::endl;
col = m.col;
raw = m.raw;
data = new int*[raw];
for (int i = 0; i < raw; ++i) {
data[i] = new int[col];
for (int j = 0; j < col; j++) {
data[i][j] = m.data[i][j];
}:q
}
}
Matrix operator+(const Matrix & m) {
if(this->raw != m.raw||this->col != m.col){
std::cout << "some error" << std::endl;
}
std::cout << "加法操作" << std::endl;
Matrix a(m.raw, m.col);
return a;
}
~Matrix(){
col = 0;
raw = 0;
for (int i = 0; i < raw; ++i) {
delete []data[i];
data[i] = nullptr;
}
delete[]data;
data = nullptr;
}
private:
int ** data = nullptr;
int col=0, raw=0;
};
int main(int argc, char *argv[])
{
Matrix a(2,3);
Matrix b(2,3);
Matrix c(2,3);
Matrix d(2,3);
Matrix e = a + b + c + d;
return 0;
}
在linux的环境下面,通过以下命令编译运行
!g++ test.cc -fno-elide-constructors -o /tmp/a.out && /tmp/a.out
-fno-elide-constructors 参数主要是关闭编译器优化,因为函数返会涉及一次拷贝动作,编译器优化会将这个给优化掉,打印结果就不会有拷贝构造的次数。
前面四次是调用构造函数,然后每一次加法操作会产生一次构造和一次拷贝构造,最后结果会有一次拷贝构造。
这么多次的拷贝,我们可以通过什么方式来优化呢?
优化
在C++11中,提出了右值引用,可以通过右值引用来延长将亡值的生命周期,具体是什么样呢?我们举个例子:
Test getTest() {
// 将亡值
return Test();
}
void setTest(Test & t){
...
}
void setTest1(Test && t){
}
int main(int argc, const char * argv[]) {
setTest(getTest()); // 编译报错, 告诉t必须是一个左值,由于右值不能进行直接引用 如同 int &a = 1;
setTest1(getTest()); // 通过,因为右值引用会将这个临时对象中的数据拿出来给自己用
}
通过这样一个例子,我们就可以发现,右值引用如何让将亡值转向引用。而在上面的案例中每一次加法中都会产生一些将亡值,可以通过右值引用避免对将亡值的拷贝,从而减少数据拷贝的次数,接下来我们将右值引用运用到上面的案例中去。
class Matrix{
public:
Matrix(int a, int b):col(b),raw(a){
std::cout << "Matix(int &a, int &b):col(b),raw(a)" << std::endl;
data = new int*[raw];
for (int i = 0; i < raw; ++i) {
data[i] = new int[col];
for (int j = 0; j < col; j++) {
data[i][j] = i+j;
}
}
}
// 引入右值引用的移动构造函数
Matrix(Matrix&& m){
std::cout << "Matrix(Matrix&& m)" << std::endl;
raw = m.raw;
col = m.col;
data = m.data; //这里直接采用里面的数据,不用进行拷贝
m.data = nullptr;
}
Matrix(const Matrix & m){
std::cout << "Matrix(Matrix & m)" << std::endl;
col = m.col;
raw = m.raw;
data = new int*[raw];
for (int i = 0; i < raw; ++i) {
data[i] = new int[col];
for (int j = 0; j < col; j++) {
data[i][j] = m.data[i][j];
}
}
}
Matrix operator+(const Matrix & m)& {
if(this->raw != m.raw||this->col != m.col){
std::cout << "some error" << std::endl;
}
std::cout << "加法操作" << std::endl;
Matix a(m.raw, m.col);
return a;// 拷贝构造
}
Matrix operator + (const Matrix &a) && /*区分第一个参数*/ {
if(this->raw != a.raw||this->col != a.col){
std::cout << "some error" << std::endl;
}
std::cout << "加法操作" << std::endl;
// 右值引用
Matrix res = move(*this);
return res;// 移动构造
}
~Matrix(){
col = 0;
raw = 0;
for (int i = 0; i < raw; ++i) {
delete []data[i];
data[i] = nullptr;
}
delete[]data;
data = nullptr;
}
private:
int ** data = nullptr;
int col=0, raw=0;
};
int main(int argc, char *argv[])
{
Matrix a(2,3);
Matrix b(2,3);
Matrix c(2,3);
Matrix d(2,3);
Matrix e = a + b + c + d;
return 0;
}
从上面的结果显示来看,通过引入右值引用,在每一次加法中都采用的移动构造函数,它基于右值引用可以有效地避免数据的拷贝赋值。
好啦,本次C++的学习就到这里了。。。
参考
https://www.bilibili.com/video/BV15v411g7Ma/?spm_id_from=333.788.recommend_more_video.-1