惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
V
V2EX
爱范儿
爱范儿
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
罗磊的独立博客
小众软件
小众软件
Vercel News
Vercel News
博客园 - 司徒正美
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
J
Java Code Geeks
P
Proofpoint News Feed
MongoDB | Blog
MongoDB | Blog
B
Blog
美团技术团队
量子位

博客园 - 平凡人

std::atomic linux学习-目录 C++设计模式--PIMPL c++的RAII blender学习系列之--CLOG Blender中的DNA QT元对象系统(Meta-Object System) Blender文档翻译:ID Datablocks(ID数据块) Blender内存管理库(bf_intern_guardedalloc) IDA\Windbg调试 - 平凡人 - 博客园 IOCTL(CTL_CODE) 内核开发速查表 Blender中的事件处理器 Blender:SDNA QT信号和槽 利用QEventLoop实现同步等待信号槽返回结果 Blender文档翻译:Operators tutorial(操作教程) Blender文档翻译:Operators Blender文档翻译:RNA Blender文档翻译-Blender库数据的通用属性
C++技术之---标准容器的值语义
平凡人 · 2026-01-23 · via 博客园 - 平凡人

std::vector可以看作值类型(value type)。在C++中,std::vector设计为具有值语义,这符合C++标准库容器的整体设计哲学。

核心特征:值语义

1. 深拷贝行为

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = v1;  // 深拷贝,v1和v2是独立的对象

v2.push_back(4);
// v1: {1, 2, 3}
// v2: {1, 2, 3, 4} - 修改不影响v1

2. 独立的生命周期

{
    std::vector<int> local = {1, 2, 3};
    auto copy = local;  // 完全独立的副本
} // local和copy都正确销毁,互不影响

3. 支持移动语义

std::vector<int> createVector() {
    std::vector<int> v(1000000);
    return v;  // 可以移动,避免深拷贝
}

auto v = createVector();  // 移动构造,高效

为什么std::vector是值类型

标准库设计原则

// 1. 容器管理自己的内存
class vector {
private:
    T* m_data;  // 容器拥有数据
    size_t m_size;
    size_t m_capacity;
};

// 2. 遵循RAII原则
{
    std::vector<int> v;  // 构造函数分配资源
    // 使用v
} // 析构函数自动释放资源

// 3. 提供完整的拷贝控制
std::vector<int> a = {1, 2, 3};
std::vector<int> b = a;  // 拷贝构造
b = a;                   // 拷贝赋值
std::vector<int> c = std::move(a);  // 移动构造

与其他值类型的比较

// 内置类型:完全值语义
int x = 10;
int y = x;  // 值拷贝

// std::vector:类似内置类型的值语义
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = v1;  // 行为类似int的拷贝

// 指针:引用语义
int* p1 = new int(10);
int* p2 = p1;  // 共享同一对象

值语义的实际表现

在函数参数中的行为

// 值传递:创建独立副本
void modifyVector(std::vector<int> v) {
    v.push_back(99);  // 不影响原vector
}

// 引用传递:操作原对象
void modifyVectorRef(std::vector<int>& v) {
    v.push_back(99);  // 修改原vector
}

// const引用:只读访问
void readVector(const std::vector<int>& v) {
    // 可以读取但不能修改
}

在容器中的表现

// vector中的vector也是值类型
std::vector<std::vector<int>> matrix = {
    {1, 2, 3},
    {4, 5, 6}
};

auto row = matrix[0];  // 深拷贝第一行
row.push_back(4);      // 不影响原matrix

与C#/Java容器的对比

// C++ - 值语义
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = v1;  // 完全独立副本
v2[0] = 100;  // v1不受影响

// C# - 引用语义(默认)
// List<int> list1 = new List<int> {1, 2, 3};
// List<int> list2 = list1;  // 共享同一对象
// list2[0] = 100;  // list1也变了

性能考虑和优化

1. 避免不必要的拷贝

// 不好:可能产生不必要的拷贝
std::vector<int> process(std::vector<int> input) {
    // 处理...
    return input;  // 可能触发拷贝
}

// 更好:使用引用或移动
std::vector<int> process(const std::vector<int>& input) {
    std::vector<int> result = input;  // 明确拷贝
    // 处理result...
    return result;  // 可能移动
}

// 或使用移动语义
std::vector<int> process(std::vector<int>&& input) {
    // 直接使用input,避免拷贝
    return std::move(input);
}

2. 返回值优化(RVO/NRVO)

std::vector<int> createVector() {
    std::vector<int> result(1000);
    // 填充数据...
    return result;  // 编译器优化,避免拷贝
}

auto v = createVector();  // 直接在v的位置构造

特例和注意事项

1. 包含指针的vector

std::vector<int*> ptrs;
ptrs.push_back(new int(10));
auto copy = ptrs;  // 浅拷贝指针,共享动态内存

// 需要手动管理或使用智能指针
std::vector<std::unique_ptr<int>> smart_ptrs;
// auto copy2 = smart_ptrs;  // 错误!unique_ptr不可拷贝

2. 自定义类型的vector

class MyClass {
    int* data;
public:
    // 需要正确实现拷贝控制
    MyClass(const MyClass& other) {
        data = new int(*other.data);  // 深拷贝
    }
    ~MyClass() { delete data; }
};

std::vector<MyClass> objects;
// vector的拷贝依赖于MyClass的正确实现

3. 内存连续性保证

std::vector<int> v = {1, 2, 3};
int* p = v.data();  // 连续内存

// 但要注意:某些操作可能使迭代器失效
auto it = v.begin();
v.push_back(4);  // 可能导致重新分配
// it可能失效

设计哲学启示

C++核心哲学

// 1. 零开销抽象
// std::vector提供安全接口,但编译后接近手写数组的性能

// 2. 资源获取即初始化(RAII)
{
    std::vector<int> v(1000);  // 构造函数分配
    // 使用...
} // 析构函数自动释放

// 3. 值语义优先
template<typename T>
void sort(std::vector<T> items) {  // 接收副本
    std::sort(items.begin(), items.end());
    // 原vector不变
}

最佳实践总结

  1. std::vector视为值类型设计代码
  2. 默认使用值语义,明确需要共享时才用引用
  3. 利用移动语义优化性能
  4. 注意包含指针的情况,可能需要深拷贝
  5. 接口设计时明确语义
    // 值传递:函数需要副本
    void processCopy(std::vector<int> data);
    
    // 引用传递:函数修改原数据
    void processInPlace(std::vector<int>& data);
    
    // const引用:只读访问
    void analyze(const std::vector<int>& data);
    

结论std::vector确实是值类型,这是C++标准库设计的核心特性之一。理解这一点对于编写正确、高效和可维护的C++代码至关重要。

在C++标准库中,绝大多数类型都是设计为值语义的。这是C++标准库的核心设计哲学之一。

完整的值类型分类

1. 所有标准库容器(除array外都是动态分配)

// 序列容器
std::vector<T>      // 动态数组
std::deque<T>       // 双端队列
std::list<T>        // 双向链表
std::forward_list<T> // 单向链表

// 关联容器
std::set<T>                    // 红黑树集合
std::multiset<T>               // 允许重复
std::map<K, V>                 // 红黑树映射
std::multimap<K, V>            // 允许重复键

// 无序关联容器(哈希表)
std::unordered_set<T>
std::unordered_multiset<T>
std::unordered_map<K, V>
std::unordered_multimap<K, V>

// 容器适配器(底层使用其他容器)
std::stack<T, Container>
std::queue<T, Container>
std::priority_queue<T, Container>

2. 字符串类

std::string           // 最常用的字符串
std::wstring          // 宽字符
std::u8string         // UTF-8 (C++20)
std::u16string        // UTF-16
std::u32string        // UTF-32

// 字符串视图(只读引用,轻量级)
std::string_view      // C++17
std::wstring_view

3. 智能指针(特殊的值类型)

// 独占所有权 - 严格值语义,但指向的对象是共享的
std::unique_ptr<T>    // 不可拷贝,只可移动

// 共享所有权 - 引用计数
std::shared_ptr<T>    // 拷贝增加引用计数

// 弱引用
std::weak_ptr<T>      // 不增加引用计数

4. 工具类和包装器

// 可选值
std::optional<T>      // C++17

// 变体类型
std::variant<T...>    // C++17

// 任意类型
std::any              // C++17

// 函数包装器
std::function<Signature>

// 持续时间
std::chrono::duration<Rep, Period>
std::chrono::time_point<Clock, Duration>

// 复数
std::complex<T>

// 大数
std::valarray<T>

5. 元组和对

std::pair<T1, T2>     // 二元组
std::tuple<T...>      // 多元组

6. 数组和bitset

std::array<T, N>      // 固定大小数组(完全值语义)
std::bitset<N>        // 位集

7. 迭代器(大部分是值类型)

// 所有标准迭代器都是可拷贝的值类型
std::vector<int>::iterator
std::map<int, int>::const_iterator
std::back_insert_iterator<std::vector<int>>

例外情况(非纯粹值类型)

1. 引用包装器

std::reference_wrapper<T>  // 包装引用,模拟值语义
int x = 10;
auto ref = std::ref(x);    // 实际上存储指针

2. 流对象

std::iostream        // 通常不可拷贝,只可移动
std::fstream
std::stringstream

// 流的状态是共享的
std::cout  // 全局单例,非值语义

3. 随机数引擎

std::mt19937  // 有状态,可拷贝但通常不推荐共享

设计模式对比

典型的值类型实现

template<typename T>
class ValueTypeExample {
private:
    T* data;           // 拥有资源
    size_t size;
    
public:
    // 五大函数(Rule of Five)
    ValueTypeExample();                     // 默认构造
    ~ValueTypeExample();                    // 析构
    
    ValueTypeExample(const ValueTypeExample&);  // 拷贝构造
    ValueTypeExample& operator=(const ValueTypeExample&); // 拷贝赋值
    
    ValueTypeExample(ValueTypeExample&&) noexcept;  // 移动构造
    ValueTypeExample& operator=(ValueTypeExample&&) noexcept; // 移动赋值
};

值语义的层次结构

// 1. 基础值类型(POD)
int, double, char*  // 内置类型

// 2. 复合值类型
struct Point { int x, y; };

// 3. 资源管理值类型
std::vector<T>      // 管理动态数组
std::string         // 管理字符串缓冲区

// 4. 复杂值类型
std::map<K, V>      // 管理红黑树
std::shared_ptr<T>  // 管理共享所有权

为什么标准库偏爱值语义

哲学基础

// 1. 确定性
{
    auto v1 = std::vector<int>{1, 2, 3};
    auto v2 = v1;  // 明确知道发生拷贝
    // 作用域结束,资源明确释放
}

// 2. 局部推理
void process(std::vector<int> data) {
    // 不需要担心data被其他代码修改
    // 函数的行为完全由输入决定
}

// 3. 组合性
std::vector<std::map<std::string, std::vector<int>>>
// 每个组件都是自包含的值

性能优化技术

// 1. 小对象优化(SSO)
std::string s1 = "short";    // 可能存储在栈上
std::string s2 = "very long string...";  // 堆分配

// 2. 移动语义
auto createBigVector() -> std::vector<int> {
    std::vector<int> v(1'000'000);
    return v;  // 可能移动而非拷贝
}

// 3. 写时拷贝(某些实现)
std::string早期实现使用COW,但C++11后不推荐

实际使用示例

值类型的组合使用

// 复杂的值类型结构
struct StockPortfolio {
    std::string owner;
    std::unordered_map<std::string,  // 股票代码
        std::pair<int, double>>      // 持股数和成本价
        holdings;
    std::vector<Transaction> history;
    
    // 自动获得正确的拷贝语义
    StockPortfolio(const StockPortfolio&) = default;
    StockPortfolio& operator=(const StockPortfolio&) = default;
};

// 使用
auto portfolio1 = StockPortfolio{/*...*/};
auto portfolio2 = portfolio1;  // 深拷贝所有数据

与现代C++特性的结合

// 值语义 + 移动语义 + 模板
template<typename T>
class Matrix {
    std::vector<std::vector<T>> data;
public:
    // 移动优化
    Matrix(Matrix&& other) noexcept = default;
    
    // 完美转发构造
    template<typename... Args>
    Matrix(Args&&... args) : data(std::forward<Args>(args)...) {}
};

// 使用
auto m1 = Matrix<int>(100, 100);  // 100x100矩阵
auto m2 = std::move(m1);  // 高效移动

最佳实践总结

何时使用值类型

// 1. 数据所有权明确
auto data = std::vector<int>{1, 2, 3};

// 2. 需要独立副本
auto backup = data;  // 创建快照

// 3. 返回值
std::vector<int> getData() {
    return {1, 2, 3};  // 值返回,可能优化
}

// 4. 多线程安全(每个线程有自己的副本)

何时使用引用/指针

// 1. 大型对象且不需要副本
void process(const std::vector<int>& largeData);

// 2. 需要共享状态
class Observer {
    std::shared_ptr<Subject> subject;
};

// 3. 多态
std::unique_ptr<Base> obj = createDerived();

结论

C++标准库的绝大多数类型都遵循值语义设计,这带来了:

  • 可预测的行为
  • 简单的内存管理
  • 良好的组合性
  • 线程安全的潜力

但也要注意:

  • 值语义可能导致不必要的拷贝(用移动语义优化)
  • 深层嵌套结构拷贝成本高(需要谨慎设计)
  • 某些特殊类型(如流)采用不同的设计

理解标准库中的值类型是编写现代C++代码的基础,这种设计哲学贯穿了整个C++生态系统。