面试题答案
一键面试1. 按值传递时编译器对移动语义的利用优化
在C++11之前,按值传递对象时,会进行对象的拷贝,这意味着会调用拷贝构造函数,对于大型对象,这可能会带来显著的性能开销。引入移动语义后,编译器在合适的情况下,会使用移动构造函数来代替拷贝构造函数。
当一个临时对象(右值)作为参数按值传递给函数时,编译器会尝试使用移动构造函数。因为临时对象即将被销毁,没有必要进行深拷贝,而是可以直接“窃取”其资源(如动态分配的内存等),将资源的所有权转移给新的对象,从而避免不必要的拷贝操作,提高性能。
2. 移动构造函数在按值传递场景中的作用
移动构造函数的作用是将一个对象的资源(例如动态分配的内存)转移到另一个对象,而不是进行深拷贝。其一般形式为:
class MyClass {
public:
MyClass(MyClass&& other) noexcept {
// 窃取other的资源
this->data = other.data;
other.data = nullptr;
}
private:
int* data;
};
在按值传递场景中,当函数参数为右值(临时对象)时,会调用移动构造函数。这样,新的对象直接获取临时对象的资源,而临时对象被置于可析构的状态(如上述例子中other.data
被设为nullptr
),避免了资源的重复分配和拷贝,大大提高了性能。
3. 移动赋值运算符在按值传递场景中的作用
移动赋值运算符与移动构造函数类似,用于将一个对象的资源转移给另一个已存在的对象。其一般形式为:
class MyClass {
public:
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
// 释放当前对象资源
delete[] data;
// 窃取other的资源
data = other.data;
other.data = nullptr;
}
return *this;
}
private:
int* data;
};
在按值传递场景中,如果函数参数是右值,且在函数内部对该参数对象进行赋值操作,移动赋值运算符会被调用。同样是通过转移资源而非拷贝来提高效率。
4. 确保代码在有移动语义和无移动语义场景下的正确性和高效性
- 正确性:
- 提供正确的拷贝构造函数和拷贝赋值运算符,以确保在需要拷贝(如左值传递时)时对象的状态能被正确复制。
- 确保移动构造函数和移动赋值运算符正确转移资源,并且不会导致悬空指针等错误。例如,在移动操作后,原对象要处于一个有效的、可析构的状态。
- 对于含有指针成员的类,在拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符中,都要正确处理指针的操作,避免内存泄漏和悬空指针。
- 高效性:
- 对于大型对象,优先使用移动语义。在函数参数和返回值中,尽量使用右值引用,让编译器能够使用移动构造函数和移动赋值运算符。
- 如果一个类不需要移动语义(如不可变类),可以将移动构造函数和移动赋值运算符声明为
delete
,以防止意外调用,同时也能让编译器进行更好的优化。 - 对于性能敏感的代码段,使用
noexcept
修饰移动构造函数和移动赋值运算符,这样编译器可能会进行更多的优化,例如在std::vector
等容器中,noexcept
的移动构造函数和移动赋值运算符会影响容器的插入和删除操作的性能。