- 移动语义
移动语义是c++11最为重要的特性之一,但这不代表着我们可以在任何时候都无脑地使用它。
在以下几个情况下,移动语义并没有什么用处。
- 没有移动操作:要移动的对象没有提供移动操作,所以移动的写法也会变成复制操作。
比如对于STL库中的array容器而言,他的元素都直接存储在了对象当中,并没有一个指向数组首个元素的指针用来进行移动操作。
在这种情况下,即使利用move函数将他强制转换为一个右值对象,也无法进行移动操作。
- 移动不会更快:要移动的对象提供的移动操作并不比复制速度更快。
std::string
提供了常数时间的移动操作和线性时间的复制操作。这听起来移动比复制快多了,但是可能不一定。
许多字符串的实现采用了小字符串优化(small string optimization,SSO)。
“小”字符串(比如长度小于15个字符的)存储在了std::string
的缓冲区中,并没有存储在堆内存,移动这种存储的字符串并不必复制操作更快。
- 移动不可用:进行移动的上下文要求移动操作不会抛出异常,但是该操作没有被声明为
noexcept
。
标准库中的某些容器操作提供了强大的异常安全保证,确保依赖那些保证的C++98的代码在升级到C++11且仅当移动操作不会抛出异常,从而可能替换操作时,不会不可运行。
结果就是,即使类提供了更具效率的移动操作,而且即使移动操作更合适(比如源对象是右值),编译器仍可能被迫使用复制操作,因为移动操作没有声明noexcept
。
- 完美转发
完美转发并不完美,它不能保证总是正确无误地转发对象。
当模板类型推导失败或者推导出错误类型,完美转发会失败。
template<typename T> void fwd(T&& param) //接受任何实参 { f(std::forward<T>(param)); //转发给f }
模版类型推导失败导致无法转发很好理解,推导出错误类型也会失败则是因为
给定我们的目标函数f
和转发函数fwd
,如果f
使用某特定实参会执行某个操作,但是fwd
使用相同的实参会执行不同的操作,完美转发就会失败。
对于推导错类型,“错误”可能意味着fwd
的实例将无法使用推导出的类型进行编译,
但是也可能意味着使用fwd
的推导类型调用f
,与用传给fwd
的实参直接调用f
表现出不一致的行为。
这种不同行为的原因可能是因为f
是个重载函数的名字,并且由于是“不正确的”类型推导,在fwd
内部调用的f
重载和直接调用的f
重载不一样。
请记住:
- 当模板类型推导失败或者推导出错误类型,完美转发会失败。
- 导致完美转发失败的实参种类有花括号初始化,作为空指针的
0
或者NULL
,仅有声明的整型static const
数据成员,模板和重载函数的名字,位域。