最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

C++11新特性大揭秘:优化性能与简化代码的利器

网站源码admin7浏览0评论

C++11新特性大揭秘:优化性能与简化代码的利器

C++11当中的{}与传统的{}

C++11之前的{}初始化

传统的{}初始化(C++03以及之前)——传统C++中, {}主要用于聚合初始化,仅用于聚合类型。 聚合类型:

  • 结构体/类:无用户自定义构造函数、无基类、无非公有成员、无虚函数。
  • 数组:固定长度的原生数组。

示例:

代码语言:javascript代码运行次数:0运行复制
// 结构体初始化
struct Point { int x; int y; };
Point p = {10, 20}; // 聚合初始化

// 数组初始化
int arr[] = {1, 2, 3}; 

// 枚举初始化(仅限C++11前部分编译器扩展)
enum Color { Red, Green, Blue };
Color c = {Red};

限制:

  • 不能用于非聚合类型(如包含构造函数的类)。
  • 不支持动态容器(如 std::vector)的直接初始化。

C++11之后的 {} 初始化(列表初始化)

C++11引入了统一初始化语法(Uniform Initialization),扩展了 {} 的用途,使其适用于所有类型的初始化。

核心特性:

  • 适用所有类型:包括基本类型、类对象、STL容器等。
  • 防止窄化转换(Narrowing Conversion):禁止隐式截断(如 double → int 无显式转换时报错)。
  • 支持 std::initializer_list:允许类定义接受初始化列表的构造函数。 示例:
代码语言:javascript代码运行次数:0运行复制
// 基本类型
int x{5};       // 替代 int x = 5;
int y{};        // 值初始化(0)

// 类对象
class Widget {
public:
    Widget(int a, double b) { /* ... */ }
};
Widget w1{10, 3.14}; // 调用构造函数
Widget w2{};          // 默认构造函数

// STL容器
std::vector<int> v{1, 2, 3}; // 初始化列表构造函数
std::map<int, std::string> m{{1, "a"}, {2, "b"}};

// 动态分配对象
int* ptr = new int[3]{1, 2, 3};

// 函数返回值
std::vector<int> create_vec() { return {1, 2, 3}; }

类成员初始化(C++11新增)

代码语言:javascript代码运行次数:0运行复制
class MyClass {
private:
    int a{10};       // 类内成员初始化
    std::string s{"Hello"};
};

防止最令人烦恼的解析

代码语言:javascript代码运行次数:0运行复制
Widget w1();  // 函数声明(传统问题)
Widget w2{};  // 明确调用默认构造函数

C++11前后{}的比较

在这里插入图片描述

C++11中的std::initializer_list

std::initializer_list 的优先级 若类同时定义了接受 std::initializer_list 的构造函数和其他构造函数,编译器会优先匹std::initializer_list,可能导致意外行为:

代码语言:javascript代码运行次数:0运行复制
std::vector<int> v1(5, 1);  // 5个元素,每个为1
std::vector<int> v2{5, 1};  // 2个元素:5和1

空 {} 执行值初始化(Value Initialization):

  • 基本类型初始化为 0 或 nullptr。
  • 类类型调用默认构造函数。 自动类型推导 使用 auto 时需注意类型推导:
代码语言:javascript代码运行次数:0运行复制
auto x{5};    // C++11中推导为 std::initializer_list<int>
auto y = {5}; // 同上
auto z(5);    // 推导为 int
// C++17修复此问题:auto x{5} 推导为 int

示例代码对比:

代码语言:javascript代码运行次数:0运行复制
// 传统初始化(C++03)
int arr[] = {1, 2, 3};
Point p = {10, 20};

// C++11列表初始化
std::vector<int> nums{1, 2, 3};
Widget w{3, 2.7};

// 窄化转换错误
double d = 3.14;
int a{d};  // 错误:窄化转换(C++11报错)
int b(d);  // 允许(但可能丢失精度)

C++11当中的右值引用

前言:C++98的C++语法中就有引⽤的语法,⽽C++11中新增了的右值引⽤语法特性,C++11之后我们之前学习的引⽤就叫做左值引⽤。⽆论左值引⽤还是右值引⽤,都是给对象取别名。

如何区别左值(Lvalue)与右值(Rvalue)

左值:有明确内存地址、可被取地址的表达式(如变量、函数返回的左值引用)。

代码语言:javascript代码运行次数:0运行复制
int a = 10;     // a 是左值
int& ref = a;   // ref 是左值引用

右值:临时对象、字面量、表达式计算的中间结果(如 x + y、函数返回的非引用类型)。

代码语言:javascript代码运行次数:0运行复制
42;             // 字面量是右值
int&& rref = 10; // 右值引用绑定到字面量

值得⼀提的是,左值的英⽂简写为lvalue,右值的英⽂简写为rvalue。传统认为它们分别是left value、right value 的缩写。现代C++中,lvalue被解释为loactorvalue的缩写,可意为存储在内存中、有明确存储地址可以取地址的对象,⽽rvalue被解释为readvalue,指的是那些可以提供数据值,但是不可以寻址,例如:临时变量,字⾯量常量,存储于寄存器中的变量等,也就是说左值和右值的核⼼区别就是能否取地址。

右值引用语法 右值引用通过 && 声明,只能绑定到右值。

代码语言:javascript代码运行次数:0运行复制
int x = 10;
int&& r1 = x;        // 错误!不能绑定到左值
int&& r2 = x + 1;    // 正确!绑定到临时结果(右值)

C++11右值引用核心工具:std::move(标记可移动对象)、std::forward(保持值类别)。

右值引用的核心作用:移动语义

问题背景:深拷贝的开销 传统 C++ 中,对象传递或赋值时默认使用 深拷贝。对于持有动态资源(如堆内存)的类,频繁拷贝会导致性能问题:

代码语言:javascript代码运行次数:0运行复制
class BigData 
{
public:
    BigData(size_t size) : data(new int[size]) {}
    ~BigData() { delete[] data; }
    // 拷贝构造函数(深拷贝)
    BigData(const BigData& other) : data(new int[other.size]) 
    {
        std::copy(other.data, other.data + size, data);
    }
private:
    int* data;
};

BigData a(1000);
BigData b = a; // 深拷贝:复制 1000 个元素(高开销)

移动构造函数与移动赋值运算符 右值引用允许定义 移动语义,直接将资源从临时对象“窃取”过来,避免深拷贝:

代码语言:javascript代码运行次数:0运行复制
class BigData 
{
public:
    // 移动构造函数
    BigData(BigData&& other) noexcept 
        : data(other.data) 
        {  // 直接接管资源
        	other.data = nullptr; // 原对象置空(避免重复释放)
    	}
    // 移动赋值运算符
    BigData& operator=(BigData&& other) noexcept 
    {
        if (this != &other) 
        {
            delete[] data;      // 释放当前资源
            data = other.data;   // 接管资源
            other.data = nullptr;
        }
        return *this;
    }
private:
    int* data;
};

BigData a(1000);
BigData b = std::move(a); // 调用移动构造函数(零拷贝)

关键函数 std::move

std::move 将左值强制转换为右值引用,标记对象可被移动。 调用后,原对象处于“有效但未定义状态”(通常不再使用)。

代码语言:javascript代码运行次数:0运行复制
BigData a(1000);
BigData b = std::move(a); // a 的资源被转移给 b
// 此时 a.data 为 nullptr

右值引用的核心作用:完美转发

问题背景:参数转发中的值类别丢失 模板函数转发参数时,若直接传递参数,可能丢失其原始值类别(左值/右值):

代码语言:javascript代码运行次数:0运行复制
template<typename T>
void wrapper(T arg) 
{
    func(arg); // arg 始终是左值,无法保持右值特性
}

wrapper(10);    // 传入右值,但 arg 变为左值

通用引用与 std::forward

  • 通用引用(Universal Reference):通过 T&& 声明的模板参数,可绑定到左值或右值。
  • std::forward 根据模板参数 T 的类型,保持参数的原始值类别。
代码语言:javascript代码运行次数:0运行复制
template<typename T>
void wrapper(T&& arg) 
{       // 通用引用
    func(std::forward<T>(arg)); // 完美转发
}

int x = 10;
wrapper(x);        // 转发左值
wrapper(10);       // 转发右值

右值引用的应用场景

优化容器操作 STL 容器(如 std::vector)利用移动语义减少元素拷贝:

代码语言:javascript代码运行次数:0运行复制
std::vector<std::string> v;
std::string s = "Hello";
v.push_back(s);         // 拷贝构造(深拷贝)
v.push_back(std::move(s)); // 移动构造(高效)

工厂函数返回对象 C++11 后,函数返回局部对象时,编译器会自动使用移动语义(若存在移动构造函数):

代码语言:javascript代码运行次数:0运行复制
BigData createData() 
{
    BigData data(1000);
    return data; // 优先调用移动构造函数(而非拷贝)
}

实现高性能智能指针 如 std::unique_ptr 的移动语义确保资源唯一所有权:

代码语言:javascript代码运行次数:0运行复制
std::unique_ptr<int> p1 = std::make_unique<int>(10);
std::unique_ptr<int> p2 = std::move(p1); // p1 变为 nullptr

引⽤折叠

C++中不能直接定义引⽤的引⽤如int& && r = i; 这样写会直接报错,通过模板或typedef中的类型操作可以构成引⽤的引⽤。

通过模板或typedef中的类型操作可以构成引⽤的引⽤时,这时C++11给出了⼀个引⽤折叠的规则:右值引⽤的右值引⽤折叠成右值引⽤,所有其他组合均折叠成左值引⽤。

下⾯的程序中很好的展⽰了模板和typedef时构成引⽤的引⽤时的引⽤折叠规则,⼤家需要⼀个⼀个仔细理解⼀下。

像f2这样的函数模板中,T&&x参数看起来是右值引⽤参数,但是由于引⽤折叠的规则,他传递左值时就是左值引⽤,传递右值时就是右值引⽤,有些地⽅也把这种函数模板的参数叫做万能引⽤。

Function(T&&t)函数模板程序中,假设实参是int右值,模板参数T的推导int,实参是int左值,模板参数T的推导int&,再结合引⽤折叠规则,就实现了实参是左值,实例化出左值引⽤版本形参的Function,实参是右值,实例化出右值引⽤版本形参的Function

代码语言:javascript代码运行次数:0运行复制
// 引用折叠的例子
template<class T>
void f1(T& x) {
    // 引用折叠限定,f1实例化以后总是一个左值引用
}

template<class T>
void f2(T&& x) {
    // 引用折叠限定,f2实例化后可以是左值引用,也可以是右值引用
}

int main() {
    typedef int& lref;  // lref是int&的别名
    typedef int&& rref; // rref是int&&的别名
    int n = 0;

    lref& r1 = n;  // r1 的类型是 int&
    lref&& r2 = n; // r2 的类型是 int&
    rref& r3 = n;  // r3 的类型是 int&
    rref&& r4 = 1; // r4 的类型是 int&&

    // 没有引用折叠时 -> f1实例化为 void f1(int& x)
    f1<int>(n);  // 正常编译
    f1<int>(0);  // 编译报错,0是右值

    // 引用折叠时 -> f1实例化为 void f1(int& x)
    f1<int&>(n); // 正常编译
    f1<int&>(0); // 编译报错,0是右值

    // 引用折叠时 -> f1实例化为 void f1(int&& x)
    f1<int&&>(n); // 编译报错,n是左值
    f1<int&&>(0); // 正常编译
	
	// 折叠->实例化为
	f1<const int&>(n);
	f1<const int&>(0);

	// 折叠->实例化为
	f1<const int&&>(n);
	f1<const int&&>(0);
	
	// 没有折叠时,f2实例化为 void f2(int&& x)
    f2<int>(n);  // 编译报错,n 是左值
    f2<int>(0);  // 正常编译,0 是右值

    // 折叠时,实例化为 void f2(int& x)
    f2<int&>(n);  // 正常编译,n 是左值
    f2<int&>(0);  // 编译报错,0 是右值

    // 折叠时,实例化为 void f2(int&& x)
    f2<int&&>(n); // 编译报错,n 是左值
    f2<int&&>(0); // 正常编译,0 是右值
代码语言:javascript代码运行次数:0运行复制
#include <iostream>
using namespace std;

// 完美转发函数模板
template<class T>
void Function(T&& t) {
    int a = 0;
    T x = a;  // T是推导出的类型
    // x++;    // 这里会报错,具体原因取决于T的类型
    cout << &a << endl;
    cout << &x << endl << endl;
}

int main() {
    // 调用Function(10),10是右值,推导出T为int,实例化为void Function(int&& t)
    Function(10);  // 右值传递,实例化为 void Function(int&& t)

    int a;
    // 调用Function(a),a是左值,推导出T为int&,引用折叠,实例化为 void Function(int& t)
    Function(a);   // 左值传递,实例化为 void Function(int& t)

    // 调用Function(std::move(a)),std::move(a)是右值,推导出T为int,实例化为 void Function(int&& t)
    Function(std::move(a));  // 右值传递,实例化为 void Function(int&& t)

    const int b = 8;
    // 调用Function(b),b是左值,推导出T为const int&,引用折叠,实例化为 void Function(const int& t)
    Function(b);  // 左值传递,实例化为 void Function(const int& t)

    // 调用Function(std::move(b)),std::move(b)是右值,推导出T为const int,实例化为 void Function(const int&& t)
    Function(std::move(b));  // 右值传递,实例化为 void Function(const int&& t)

    return 0;
}

可变参数模板

基本语法及原理

C++11⽀持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数⽬的参数被称为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函数参数。 template <class …Args> void Func(Args… args) {} template <class …Args> void Func(Args&… args) {} template <class …Args> void Func(Args&&… args) {} 我们⽤省略号来指出⼀个模板参数或函数参数的表⽰⼀个包,在模板参数列表中,class…或typename…指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟…指出接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板⼀样,每个参数实例化时遵循引⽤折叠规则。 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。

  • 这⾥我们可以使⽤sizeof…运算符去计算参数包中参数的个数。
代码语言:javascript代码运行次数:0运行复制
#include <iostream>
#include <string>
using namespace std;

// 可变参数模板,使用参数包
template <class ...Args>
void Print(Args&&... args) 
{
    cout << sizeof...(args) << endl;  // 打印参数包中参数的个数
}

int main() 
{
    double x = 2.2;

    Print();  // 包含 0 个参数
    Print(1);  // 包含 1 个参数
    Print(1, string("xxxxx"));  // 包含 2 个参数
    Print(1.1, string("xxxxx"), x);  // 包含 3 个参数
    return 0;
}

包扩展

对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯⼀的事情就是扩展它,当扩展⼀个包时,我们还要提供⽤于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元素应⽤模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(…)来触发扩展操作。底层的实现细节如图1所⽰。 C++还⽀持更复杂的包扩展,直接将参数包依次展开依次作为实参给⼀个函数去处理。

代码语言:javascript代码运行次数:0运行复制
#include <iostream>
#include <string>
using namespace std;

void ShowList() {
    // 编译器时递归的终止条件,参数包是0个时,直接匹配这个函数 
    cout << endl;
}

template <class T, class ...Args>
void ShowList(T x, Args... args) {
    cout << x << " ";
    // args是N个参数的参数包 
    // 调用ShowList,参数包的第一个传给x,剩下N-1传给第二个参数包 
    ShowList(args...);
}

// 编译时递归推导解析参数 
template <class ...Args>
void Print(Args... args) {
    ShowList(args...);
}

int main() {
    Print();
    Print(1);
    Print(1, string("xxxxx"));
    Print(1, string("xxxxx"), 2.2);
    return 0;
}
代码语言:javascript代码运行次数:0运行复制
#include <iostream>
#include <string>
using namespace std;

template <class T>
const T& GetArg(const T& x) 
{
    cout << x << " ";
    return x;
}

template <class ...Args>
void Arguments(Args... args) {}

template <class ...Args>
void Print(Args... args) 
{
    // 注意GetArg必须返回或者传递的对象,这样才能组成参数包给Arguments 
    Arguments(GetArg(args)...);
}

// 本质可以理解为编译器编译时,包的扩展模式 
// 将上面的函数模板扩展实例化为下面的函数 
// 例如:
// void Print(int x, string y, double z)
// {
//     Arguments(GetArg(x), GetArg(y), GetArg(z));
// }

int main() 
{
    Print(1, string("xxxxx"), 2.2);
    return 0;
}

empalce系列接⼝

在C++11中,emplace 是容器类(如 std::vector、std::map、std::unordered_map 等)提供的接口,它允许在容器中原地构造元素,而不需要先构造对象再复制或移动到容器中。这样可以避免不必要的构造和复制,提高性能。

emplace的主要接口: C++11 引入了以下几个与 emplace 相关的接口:

emplace_back():用于在容器的末尾原地构造元素(适用于 std::vector、std::deque 和 std::list)。 emplace_front():用于在容器的前端原地构造元素(适用于 std::deque 和 std::list)。 emplace():用于在关联容器中插入元素(适用于 std::map、std::multimap、std::unordered_map、std::unordered_multimap 等)。

  1. emplace_back() emplace_back() 允许在 std::vector 或其他类似容器的末尾原地构造元素,而不需要先创建临时对象。

例如:

代码语言:javascript代码运行次数:0运行复制
#include <iostream>
#include <vector>

class MyClass 
{
public:
    MyClass(int a, double b) 
    {
        std::cout << "MyClass constructed with " << a << " and " << b << std::endl;
    }
};

int main() 
{
    std::vector<MyClass> vec;
    vec.emplace_back(10, 3.14);  // 原地构造 MyClass(10, 3.14)
    return 0;
}
  1. emplace_front() emplace_front() 类似于 emplace_back(),但它是在容器的前端原地构造元素(适用于 std::deque 和 std::list)。

示例:

代码语言:javascript代码运行次数:0运行复制
#include <iostream>
#include <deque>

class MyClass 
{
public:
    MyClass(int a, double b) 
    {
        std::cout << "MyClass constructed with " << a << " and " << b << std::endl;
    }
};

int main() 
{
    std::deque<MyClass> dq;
    dq.emplace_front(10, 3.14);  // 在前端原地构造 MyClass(10, 3.14)
    return 0;
}
  1. emplace() emplace() 用于关联容器(如 std::map、std::unordered_map)中插入元素。与 insert() 不同,emplace() 会直接在容器中构造元素,而不需要创建临时对象。它接受构造元素所需的参数,并原地构造该元素。

对于 std::map:

代码语言:javascript代码运行次数:0运行复制
#include <iostream>
#include <map>

int main()
{
    std::map<int, std::string> myMap;
    myMap.emplace(1, "one");  // 原地构造键值对 (1, "one")
    myMap.emplace(2, "two");
    
    for (const auto& pair : myMap) 
    {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    
    return 0;
}

为什么使用 emplace?

  • 性能优势:emplace 不需要先构造临时对象再插入,它直接在容器中构造元素,减少了不必要的复制或移动操作。
  • 更灵活的构造方式:通过 emplace,你可以传递元素构造所需的参数,容器会直接使用这些参数来构造元素,这对复杂类型特别有用。
  • 简洁性:通过 emplace,你可以避免创建临时对象并直接在容器内构造对象。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2025-03-16,如有侵权请联系 cloudcommunity@tencent 删除c++对象函数性能优化
发布评论

评论列表(0)

  1. 暂无评论