一、问题
当我尝试在\JUCE\extras\WindowsDLL\Builds\VisualStudio2022目录下编译JUCE库的时候,提示报错如下:
报错提示如下:
这里涉及到两个问题
一、这个std::move是干嘛用的
二、为什么这里会报错?
另外,我在实际的开发过程中发现这个JUCE没法编译成一个单独的库的形式,而是只能以.cpp文件和.h文件导入引用的形式使用,如果有人知道如何编译成dll,可以和我交流。
探幽
一、这个std::move是干嘛用的
我们百度可以搜到,std::move是做了移动语义,什么是移动语义?它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值。那么什么是左值右值?还需要一步步娓娓道来。
左值右值
1.左值右值的概念
C++中左值(lvalue)和右值(rvalue)是比较基础的概念,虽然平常几乎用不到,但C++11之后变得十分重要,它是理解 move/forward 等新语义的基础。
- 左值是可寻址的变量,有持久性;
- 右值一般是不可寻址的常量,或在表达式求值过程中创建的无名临时对象,短暂性的。
左值和右值主要的区别之一是左值可以被修改,而右值不能。
可以参考如下一段代码:
int a, b; // a 为左值
a = 3; // 3 为右值
b = a + 3; // a+3 为右值
b = a; // a是左值
2.左值引用和右值引用
我们说到引用的时候,大部分都是在说左值引用
- 左值引用:引用一个对象;
- 右值引用:就是必须绑定到右值的引用,C++11中右值引用可以实现“移动语义”,通过 && 获得右值引用。
或者一般情况下,也就是绑定了一个会随着右值变化而变化的值,比如:
int x = 6; // x是左值,6是右值
int &y = x; // 左值引用,y引用x
int &z1 = x * 6; // 错误,x*6是一个右值
const int &z2 = x * 6; // 正确,可以将一个const引用绑定到一个右值
int &&z3 = x * 6; // 正确,右值引用
int &&z4 = x; // 错误,x是一个左值
什么意思呢,我们可以写一段简单的代码来演示一下:
int main()
{
int x = 114;
int c = x * 2;
int&& b = x * 2;
}
在上面这两段代码中,第二行和第三行的执行逻辑不一样。
当我们走到第二行的时候,实际上是做了:
- 创建了一个临时变量temp = x * 2
- 令c拷贝一份temp变量
- delete temp变量
但我们在第三行中则稍做了些改变
- 创建了一个临时变量temp = x * 2
- 令c变为指向 temp 变量 的引用
这样可以看到,如果使用右值引用的方式,则会直接省去一个拷贝操作的开销,相对的就少了拷贝的内存开销。
二、移动语义的概念解决了什么问题?
如果一个类中涉及到资源管理,用户必须显式提供拷贝构造、赋值运算符重载以及析构函数,否则编译器将会自动生成一个默认的,如果遇到拷贝对象或者对象之间相互赋值,就会出错,比如:
class String
{
public:
String(char* str = "")
{
if (nullptr == str)
str = "";
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* pTemp = new char[strlen(s._str) + 1];
strcpy(pTemp, s._str);
delete[] _str;
_str = pTemp;
}
return *this;
}
String operator+(const String& s)
{
char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
strcpy(pTemp, _str);
strcpy(pTemp + strlen(_str), s._str);
String strRet(pTemp);
return strRet;
}
~String()
{
if (_str) delete[] _str;
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2("world");
String s3(s1 + s2);
return 0;
}
上述代码看起来没有什么问题,但是有一个不太尽人意的地方:
在operator+中:strRet在按照值返回时,必须创建一个临时对象,临时对象创建好之后,strRet就被销毁了,最后使用返回的临时对象构造s3,s3构造好之后,临时对象就被销毁了。仔细观察会发现:strRet、临时对象、s3每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完
全相同的对象,对于空间是一种浪费,程序的效率也会降低,而且临时对象确实作用不是很大,那能否对该种情况进行优化呢?
C++11提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式,可以有效缓解该问题。
我们看到 关于+号的函数如下:
String operator+(const String& s)
{
char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
strcpy(pTemp, _str);
strcpy(pTemp + strlen(_str), s._str);
String strRet(pTemp);
return strRet;
}
最大的开销出自这句拷贝构造函数:
String strRet(pTemp);
这一句话如果我们不修改原有的这个String类的话,这一句话则是一个拷贝开销,我们都知道拷贝带来的开销是比较大的,所以我们需要解决这个问题。
则需要在其构造函数里面下手:
String(String&& s)
: _str(s._str)
{
s._str = nullptr;
}
因为strRet对象的生命周期在创建好临时对象后就结束了,即将亡值,C++11认为其为右值,在用strRet构造临时对象时,就会采用移动构造,即将strRet中资源转移到临时对象中。而临时对象也是右值,因此在用临时对象构造s3时,也采用移动构造,将临时对象中资源转移到s3中,整个过程,只需要创建一块堆内存即可,既省了空间,又大大提高程序运行的效率。
注意:
- 移动构造函数的参数千万不能设置成const类型的右值引用,因为资源无法转移而导致移动语义失效。
- 在C++11中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造。