29. C++高级特性:移动语义、智能指针、lambda表达式、右值引用
各位同学,今天我们来聊聊C++里几个真正能提升代码质量的高级特性。说实话,我当年刚接触这些概念时,也觉得头大。但用久了你会发现,它们就是现代C++的基石。
先给大家画个知识地图,看看这几个特性之间的关系:
一、右值引用——移动语义的基石
先说说右值引用。很多初学者搞不清左值和右值的区别。我打个比方:左值就像你家客厅里的沙发,有固定位置,你可以反复坐上去;右值就像快递包裹,拆完就扔,没人会留着。
在C++里,左值是有名字、能取地址的表达式;右值是临时对象,比如函数返回值、字面常量。右值引用用&&声明,它专门绑定到右值上。
int a = 42; // a是左值
int&& r = 42; // 42是右值,r绑定到它
int&& r2 = a; // 编译错误!a是左值
int&& r3 = std::move(a); // 用move把左值转成右值
二、移动语义——告别深拷贝
为什么要搞移动语义?说白了就是性能。我早期做QT项目时,经常传大对象,比如QImage、QByteArray。每次拷贝都触发深拷贝,内存分配加数据复制,慢得让人抓狂。
移动语义的核心思想:与其复制资源,不如把资源的所有权"偷"过来。源对象变成空壳,目标对象拿走所有数据。
class MyBuffer {
public:
// 移动构造函数
MyBuffer(MyBuffer&& other) noexcept
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr; // 源对象置空
other.size_ = 0;
}
// 移动赋值运算符
MyBuffer& operator=(MyBuffer&& other) noexcept {
if (this != &other) {
delete[] data_; // 释放自己的资源
data_ = other.data_; // 偷走对方的资源
size_ = other.size_;
other.data_ = nullptr; // 对方置空
other.size_ = 0;
}
return *this;
}
private:
char* data_ = nullptr;
size_t size_ = 0;
};
你想想看,一个包含10MB数据的对象,拷贝要分配10MB内存再复制数据,移动只是交换几个指针。这差距,在QT里处理大图片时特别明显。
三、智能指针——告别手动delete
说到内存管理,我见过太多野指针、内存泄漏的bug了。我自己早期写C++时也犯过这种错——new了忘记delete,或者不小心把同一个指针delete了两次。
智能指针就是来解决这个问题的。C++11提供了三种:
| 类型 | 所有权 | 引用计数 | 适用场景 |
|---|---|---|---|
| unique_ptr | 独占所有权 | 无 | 资源唯一拥有者 |
| shared_ptr | 共享所有权 | 有(原子操作) | 多个对象共享资源 |
| weak_ptr | 弱引用 | 不增加计数 | 打破循环引用 |
3.1 unique_ptr——独占的王者
unique_ptr是独占所有权的智能指针。它不能被拷贝,只能被移动。这设计很巧妙——你想想,如果资源只能有一个主人,那拷贝就没意义了。
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
// std::unique_ptr<MyClass> ptr2 = ptr1; // 编译错误!不能拷贝
std::unique_ptr<MyClass> ptr3 = std::move(ptr1); // 可以移动,ptr1变空
3.2 shared_ptr——共享的代价
shared_ptr允许多个指针共享同一个对象。它内部维护一个引用计数,当最后一个shared_ptr销毁时,才释放资源。
std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>();
{
std::shared_ptr<MyClass> sp2 = sp1; // 引用计数变为2
// 使用sp2...
} // sp2销毁,引用计数变为1
// sp1销毁,引用计数变为0,对象被释放
3.3 weak_ptr——破局者
weak_ptr不增加引用计数。它只能从shared_ptr构造,使用时需要调用lock()获取一个临时的shared_ptr。
std::shared_ptr<MyClass> sp = std::make_shared<MyClass>();
std::weak_ptr<MyClass> wp = sp;
// 使用前检查是否有效
if (auto locked = wp.lock()) {
locked->doSomething(); // 安全使用
} else {
// 对象已被释放
}
四、Lambda表达式——就地编写匿名函数
Lambda表达式是C++11引入的语法糖,让你能在需要函数的地方直接写一个匿名函数。我在QT里用lambda做信号槽连接,比写独立的槽函数方便太多了。
// 基本语法:[捕获列表](参数列表) -> 返回类型 { 函数体 }
auto add = [](int a, int b) -> int { return a + b; };
int result = add(3, 4); // result = 7
// 捕获外部变量
int factor = 2;
auto multiply = [factor](int x) { return x * factor; };
std::cout << multiply(5); // 输出10
捕获方式有几种:
[=]:按值捕获所有外部变量[&]:按引用捕获所有外部变量[this]:捕获当前类的this指针[x, &y]:混合捕获,x按值,y按引用
在QT里,lambda最常见的用法就是信号槽:
// QT5风格
connect(button, &QPushButton::clicked, this, [this]() {
this->updateUI();
qDebug() << "按钮被点击了";
});
// 配合mutable关键字,可以修改按值捕获的变量
int counter = 0;
auto increment = [counter]() mutable {
return ++counter;
};
std::cout << increment(); // 输出1
std::cout << increment(); // 输出2,注意外部的counter还是0
五、综合应用——把这些特性用起来
说了这么多,咱们来个实战例子。假设你在QT里写一个图片处理工具:
class ImageProcessor {
public:
// 使用unique_ptr管理资源
using ImagePtr = std::unique_ptr<QImage>;
// 移动语义:高效传递大图片
void processImage(ImagePtr image) {
// 用lambda做异步处理
QtConcurrent::run([this, img = std::move(image)]() {
// 处理图片...
QImage processed = img->scaled(800, 600);
// 处理完成后,通过信号通知UI线程
emit imageReady(std::make_shared<QImage>(processed));
});
}
// 用shared_ptr管理多个观察者共享的图片
void broadcastImage(std::shared_ptr<QImage> sharedImg) {
for (auto& observer : observers_) {
if (auto obs = observer.lock()) { // weak_ptr安全访问
obs->onImageUpdated(sharedImg);
}
}
}
private:
std::vector<std::weak_ptr<ImageObserver>> observers_;
};
这个例子把今天讲的内容全用上了:unique_ptr管理独占资源、移动语义避免拷贝、shared_ptr共享数据、weak_ptr避免循环引用、lambda做回调。嗯,这才是现代C++该有的样子。
好了,今天的内容就到这里。这些特性是C++11的核心,也是现代C++编程的必备技能。多写多练,慢慢就会形成肌肉记忆。
公众号:蓝海资料掘金营,微信deep3321