C++常见避坑指南( 六 )


auto func = &a{};
auto func = a = std::move(a){}; (限C++14以后)
隐式类型转换
std::map<int, std::string> myMap = {{1, "One"}, {2, "Two"}, {3, "Three"}};for (const std::pair<int, std::string>& pair : myMap) {    //...}这里在遍历关联容器时,看着是const引用的,心想着不会发生拷贝,但是因为类型错了还是会发生拷贝,std::map 中的键值对是以 std::pair<const Key, T> 的形式存储的 , 其中key是常量 。因此,在每次迭代时,会将当前键值对拷贝到临时变量中 。在处理大型容器或频繁遍历时 , 这种拷贝操作可能会产生一些性能开销,所以在遍历时推荐使用const auto&,也可以使用结构化绑定:for(const auto& [key, value]: map){} (限C++17后)
函数返回值优化
RVO是Return Value Optimization的缩写,即返回值优化,NRVO就是具名的返回值优化 , 为RVO的一个变种,此特性从C++11开始支持 。为了更清晰的了解编译器的行为,这里实现了构造/析构及拷贝构造、赋值操作函数,如下:
class Widget {public: Widget() {  std::cout << "Widget: Constructor" << std::endl; }    Widget(const Widget& other) {        name = other.name;        std::cout << "Widget: Copy construct" << std::endl;    }    Widget& operator=(const Widget& other) {        std::cout << "Widget: Assignment construct" << std::endl;        name = other.name;        return *this;    }    ~Widget() {        std::cout << "Widget: Destructor" << std::endl;    }public:    std::string name;};Widget GetMyWidget(int v) {    Widget w;    if (v % 2 == 0) {        w.name = 1;        return w;    } else {        return w;    }}int main(){    const Widget& w = GetMyWidget(2); // (1)    Widget w = GetMyWidget(2); // (2)    GetMyWidget(2); // (3)    return 0;}运行上面代码,跑出的结果:
未优化:(msvc 2022, C++14)Widget: ConstructorWidget: Copy constructWidget: DestructorWidget: Destructor优化后:Widget: ConstructorWidget: Destructor针对上面(1)(2)(3)的调用,我之前也是有点迷惑,以为要减少拷贝必须得用常引用来接,但是发现编译器进行返回值优化后(1)(2)(3)运行结果都是一样的,也就是日常开发中,针对函数中返回的临时对象,可以用对象的常引用或者新的一个对象来接,最后的影响其实可以忽略不计的 。不过个人还是倾向于对象的常引用来接,一是出于没有优化时(编译器不支持或者不满足RVO条件)可以减少一次拷贝,二是如果返回的是对象的引用时可以避免拷贝 。但是也要注意不要返回临时对象的引用 。
// pb协议接口实现inline const ::PB::XXXConfig& XXConfigRsp::config() const {   //...}void XXSettingView::SetSettingInfo(const PB::XXConfigRsp& rsp){ const auto config = rsp.config(); // 内部返回的是对象的引用,这里没有引用来接导致不必要的拷贝}当遇到上面这种返回对象的引用时,外部最好也是用对象的引用来接,减少不必要的拷贝 。
此外,如果Widget的拷贝赋值操作比较耗时 , 通常在使用函数返回这个类的一个对象时也是会有一定的讲究的 。
// style 1Widget func(Args param);// style 2bool func(Widget* ptr, Args param);上面的两种方式都能达到同样的目的,但直观上的使用体验的差别也是非常明显的:

style 1只需要一行代码,而style 2需要两行代码,可能大多数人直接无脑style 1


推荐阅读