某日二师兄参加XXX科技公司的C++工程师开发岗位第16面:
int a = 42; //a是左值,可以&a
int* p = &a;
int* p = &42; //42是右值,无法取地址
int a = 42, b = 1024;
decltype(a+b); //类型为右值,a+b返回的值存在寄存器中
decltype(a+=b); //类型为左值,a+=b返回的值存储在内存中
42; //纯右值
int a = 1024;
std::move(a); //将亡值
void function(std::vector<int> vi2)
{
vi2.push_back(6);
for(auto& i: vi2) { std:: cout < i << " " ;}
std::cout << std::endl;
}
int main(int argc, char* argv[])
{
std::vector<int> vi1{1,2,3,4,5};
function(vi1);
return 0;
}
void function(std::vector<int>&& vi2)
{
vi2.push_back(6);
for(auto& i: vi2) { std:: cout < i << " " ;}
std::cout << std::endl;
}
int main(int argc, char* argv[])
{
std::vector<int> vi1{1,2,3,4,5};
function(std::move(vi1));
return 0;
}
struct Foo
{
int* data_;
//copy construct
Foo(const Foo& oth)
{
data_ = new int(*oth.data_);
}
//move construct
Foo(Foo&& oth) noexcept
{
data_ = oth.data_; //steal
oth.data_ = nullptr; //set to null
}
}
template<typename T>
void function(T&& t) { ...}
让我们来回顾以下二师兄今天的表现:
这里尽量不要省略。如果省略,编译器会推断是否会抛出异常。如果移动构造函数可能会抛出异常,则编译器不会将其标记为noexcept
。当编译器不标记为noexcept
时,为了保证程序的正确性,编译器可能会采用拷贝构造的方式实现移动构造,从而导致效率降低。
需要注意的是,如果标记了noexcept
但在移动时抛出了异常,则程序会调用std::terminate()
函数来终止运行。
这里的确是通过static_cast实现的,讲左值强行转换成右值,用来匹配移动语义而非拷贝。
template<typename T>
typename std::remove_reference<T>::type&& move(T&& t) { return static_cast<typename std::remove_reference<T>::type&&>(t);}
万能引用主要使用了引用折叠技术,
template<typename T>
void function(T&& t) { ...}
当T类型为左值时,&& &
被折叠为&
, 当T类型为右值时,&& &&
被折叠称为&&
。以下是折叠规则:
& & -> &
& && -> &
&& & -> &
&& && -> &&
当我们需要在function
中传递t参数时,如何保证它的左值或右值语义呢?这时候完美转发就登场了:
template<typename T>
void function2(T&& t2) {}
template<typename T>
void function(T&& t)
{
function2(t);
}
当传入的参数t的类型时右值时,由于引用折叠还是右值,此时的t
虽然时一个右值引用,但t
本身却是一个左值!这里非常的不好理解。如果我们把t
直接传入到function2
,那么function2
中的t2
会被推导成左值,达不到我们的目标。如果在调用function2
时传入std::move(t)
,当t
是右值时没有问题,但当t
是左值时,把t
移动到t2
,t
在外部不在能用。这也不符合我们的预期。此时std::forward
闪亮登场!
template<typename T>
void function2(T&& t2) {}
template<typename T>
void function(T&& t)
{
function2(std::forward<T&&>(t));
}
std::forward
使用了编译时多态(SFINAE
)技术,使得当参数t
是左值是和右值是匹配不同的实现,完成返回不同类型引用的目的。以下是标准库的实现:
template <typename _Tp>
constexpr _Tp && forward(typename std::remove_reference<_Tp>::type &&__t) noexcept
{
return static_cast<_Tp &&>(__t);
}
template <typename _Tp>
constexpr typename std::remove_reference<_Tp>::type && move(_Tp &&__t) noexcept
{
return static_cast<typename std::remove_reference<_Tp>::type &&>(__t);
}
好了,今日份面试到这里就结束了。二师兄的表现如何呢?预知后事如何,且听下回分解。