Skip to content

Daily Study

更新: 3/10/2026 字数: 0 字 时长: 0 分钟

Daily Plan

#todo

  • [ ]

C++回顾

左右值、复制构造函数、移动构造函数

共享指针

虚函数

static

万能引用和完美转发

目的:避免不必要的内存拷贝

  • 万能引用 :一种特殊的类型推导状态,它既能接收左值,也能接收右值。
  • 完美转发:在函数传递参数时,保持参数原来的“左/右值属性”不丢失。

这里涉及到左右值的应用,下面详细说明一下:

  • 左值引用:Type& name = var;,本质是给一个左值起别名,限制:普通的左值引用不能绑定到右值上。特例const 左值引用是“万能”的,它可以绑定右值。 const int& ref = 10; 编译器会偷偷造一个临时变量存 10,然后让 ref 指向它,但是只能读,不能改
c++
int a = 10;
int& ref1 = a;  // OK,ref1 是 a 的别名
// int& ref2 = 10; // Error! 10 是右值,不能给它起别名(因为它很快就被销毁)
  • 右值引用:Type&& name = value;本质延长了右值的生命周期,目的是为了内存资源的转移。
c++
int&& ref = 10; // OK,ref 是 10 的别名。
ref = 20;       // OK,我们可以修改这个临时的 10。
  • 移动语义:利用右值应用来做性能优化。例如,把一个临时的String赋值给另一个变量,会触发拷贝构造函数,系统需要申请新内存,把数据复制一遍,然后释放旧的内存。利用右值引用重载移动构造函数,就无需深拷贝,性能提升巨大。
c++
//deep-copy
String a = String("Hello"); // String("Hello") 是右值
String b = a; // 拷贝构造:malloc 新内存 -> memcpy 数据

// 移动构造函数 
String(String&& other) { 
    // 1. 指针交换
    this->data = other.data; 
    
    // 2. 把原来的指针置空,防止析构时 double free
    other.data = nullptr; 
}

String b = String("Hello"); // 触发移动构造

回到万能引用,当 T&& 发生类型推导时,它是万能引用,如下代码所示。核心机制是其内部的引用折叠函数(只有右值遇上右值才是右值,其他情况全是左值)

c++
int x = 10;
func(x);  // 传左值 -> T 被推导为 int&,param 变成 int&
func(20); // 传右值 -> T 被推导为 int (或 int&&),param 变成 int&&

这里存在一个问题:具名参数进入函数内部后,右值属性会丢失,因此,引入了完美转发 std::forward<T>(),来进行还原,防止原本的属性丢失如下:

c++
//错误写法
void process(int&& x) { // 万能引用,可以接所有
    // 致命错误:param 在这里虽然类型可能是右值引用, 
    // 但 param 这个变量本身有一个名字,所以它变成了“左值”!
    process_next(x); 
}

//正确写法
void wrapper(T&& param) { 
	// std::forward<T> 会根据 T 的类型,把 param 还原成最初的样子 
	real_process(std::forward<T>(param)); 
}

int main() { 
	int x = 10; 
	wrapper(x); // T=int&. forward<int&> 转化后依然是左值 -> 调用左值版本
	wrapper(20); // T=int. forward<int> 转化后变成右值 -> 调用右值版本 
	}

总结:

  • 万能引用 (T&&):在模板推导中,能接收左值也能接收右值。
  • 引用折叠:T&& 能够万能的核心机制。只有右值遇上右值才是右值,其他情况全是左值。
  • 问题的产生:函数参数在函数体内是有名字的,具名的右值引用是左值。会导致右值属性丢失。
  • 完美转发 (std::forward):用来解决上述问题。它根据模板类型 T,强制将参数转换回它原本的左/右值属性,通常用于emplace_back 或 工厂函数 中以避免不必要的拷贝。

智能指针

unique_ptr

推荐使用 std::make_unique(C++14 引入)来创建对象,这比直接使用 new 更安全。

  • 不可复制: 你不能将一个 unique_ptr 赋值或拷贝给另一个。这保证了所有权的唯一性。
  • 可移动: 你可以通过 std::move 将所有权从一个 unique_ptr 转移给另一个。转移后,原指针变为空(nullptr)。
  • 零开销: 默认情况下,unique_ptr 的大小和裸指针完全一样,它的内存释放逻辑在编译期就已经确定,没有额外的性能损耗。

shared_ptr

shared_ptr 代表共享所有权。多个 shared_ptr 可以同时指向同一个对象,该对象只有在最后一个指向它的 shared_ptr 被销毁时才会被释放。

  • 引用计数: shared_ptr 内部维护着一个控制块,其中包含一个引用计数。每当发生拷贝时,计数器加 1;每当一个 shared_ptr 离开作用域或被重置时,计数器减 1。
  • 自动释放: 当引用计数降为 0 时,内部的裸指针会被 delete。
  • 额外开销: 因为需要维护控制块和保证引用计数增减的线程安全性(原子操作),shared_ptr 的内存占用通常是裸指针的两倍,且性能略低于 unique_ptr。
  • 致命陷阱:循环引用:如果两个对象互相持有对方的 shared_ptr,它们的引用计数将永远无法降为 0,从而导致内存泄漏。为了解决这个问题,C++ 提供了 std::weak_ptr。weak_ptr 可以观测 shared_ptr 指向的对象,但不增加引用计数,通常用于打破循环引用。

weak_ptr

它可以观察并访问对象,但不会增加对象的引用计数。如下:

C++
#include <iostream>
#include <memory>

class Child;

class Parent {
public:
    std::shared_ptr<Child> childPtr; // 强引用
    Parent() { std::cout << "Parent 构造\n"; }
    ~Parent() { std::cout << "Parent 析构\n"; }
};

class Child {
public:
    std::weak_ptr<Parent> parentPtr; // 弱引用:不增加引用计数!
    Child() { std::cout << "Child 构造\n"; }
    ~Child() { std::cout << "Child 析构\n"; }
    
    void doSomething() {
        // weak_ptr 不能直接访问对象,必须先调用 lock() 提升为 shared_ptr
        if (std::shared_ptr<Parent> p = parentPtr.lock()) {
            std::cout << "成功访问 Parent 对象,此时引用计数暂时加 1\n";
        } else {
            std::cout << "Parent 对象已经被销毁\n";
        }
    }
};

int main() {
    {
        std::shared_ptr<Parent> p = std::make_shared<Parent>();
        std::shared_ptr<Child> c = std::make_shared<Child>();
        
        p->childPtr = c;     // c 的引用计数变为 2
        c->parentPtr = p;    // p 的引用计数仍然是 1 (因为 parentPtr 是 weak_ptr)

        std::cout << "p 的引用计数: " << p.use_count() << "\n"; // 输出 1
        std::cout << "c 的引用计数: " << c.use_count() << "\n"; // 输出 2
        
        c->doSomething();
    } // 离开作用域分析:
      // 1. p 被销毁,p 的引用计数从 1 降为 0 -> 触发 Parent 对象销毁。
      // 2. Parent 被销毁,其内部的 childPtr 被销毁,c 的引用计数从 2 降为 1。
      // 3. c 被销毁,c 的引用计数从 1 降为 0 -> 触发 Child 对象销毁。

    std::cout << "main 函数结束\n";
    return 0;
}

菜就多练

本站访客数 人次 本站总访问量