Sequential List

2026/6/4 860 words 4 min read

我会在这个专栏,通过「数据结构与算法」来介绍 Modern C++。相关源码放在:Data Structures and Algorithms

Unique_ptr

Unique_ptr 是智能指针之一,如需使用,则要 include <memory>
语法:std::unique_ptr<T> p = std::make_unique<T>(value);
其中:

  • T 表示对象类型;
  • std::make_unique 用于在堆上创建对象;
  • 返回值是一个 std::unique_ptr<T>

通常来说,在学校里学 C++的时候,学校会教你使用 T* p = new T(value) 来在堆上创建对象。本质上,这两种方法相同。但是使用 new,后续需要 delete 来手动释放对象。

Example:在顺序表的实现中,你需要开辟一块连续的内存。

auto p = std::make_unique<T[]>(initialCapacity)

Note

所有权指的是:若某个对象 A 拥有另一个对象 B 的所有权,那么 A 就负责 B 的生命周期管理,即 B 的创建、使用和销毁。换句话说,A 有权决定 B 什么时候被创建,什么时候被销毁。

这里会创建一个容量为 initialCapacity 的动态数组,并将其所有权交给 unique_ptr 管理。当 unique_ptr 离开作用域时,会自动释放这块内存,因此无需手动编写 delete[]

由于 unique_ptr 独享对象的所有权,因此不允许拷贝。
Example:

auto p1 = std::make_unique<int>(42);
auto p2 = p1;      // 编译错误

如果希望转移所有权,则需要使用移动语义(Move Semantics),std::move():

auto p1 = std::make_unique<int>(42);
auto p2 = std::move(p1);

此时对象的所有权从 p1 转移给 p2,而 p1 会变为空指针(nullptr)。

在 Sequential List 的实现中,我们经常会遇到扩容的需求。当数组空间不足时,需要申请一块更大的连续内存,并将原有数据迁移过去。例如:

void ensureCapacity() {

    if (size_ < capacity_) {
        return;
    }

    const std::size_t newCapacity = capacity_ * 2;

    auto newElements = std::make_unique<T[]>(newCapacity);


    for (std::size_t i = 0; i < size_; ++i) {

        newElements[i] = elements_[i];

    }


    elements_ = std::move(newElements);

    capacity_ = newCapacity;

}

我们主要关注:

elements_ = std::move(newElements);

由于 elements_newElements 都是 std::unique_ptr<T[]>,而 unique_ptr 独享对象的所有权,因此不能直接进行拷贝赋值:

elements_ = newElements; // Error

所以我们通过 std::move 将新数组的所有权被转移给了 elements_,而 newElements 会变为空指针。

Optional

我们在实现查找操作的时候,如果碰到没有找到的情况,C 的风格就是 return -1,返回一个 sentinel value。这种做法虽然简单,但并不总是可靠。对于某些类型而言,很难找到一个永远不会与正常数据冲突的特殊值。
因此在 C++17 引入了 std::optional 标准库类型,如需使用,则要 include <optional>
std::optional<T> 表示:可能包含一个 T 类型的值,也可能什么都不包含。
Example:std::optional<std::size_t> find(const T& value)

  • 如果查找成功:return i
  • 如果查找成功:return std::nullopt

其中,std:nullopt 表示当前 optional 不包含任何值。
使用时,通常需要先判断是否有值:​

auto result = find(value);
if (result) {
    std::cout << *result;
}

这里的 *result,表示获取 optional 内部保存的对象,其作用类似于对指针解引用。

当然实际函数里面还有一些 tricky 的语法:

[[nodiscard]] std::optional<std::size_t> find(const T& value) const noexcept {

	for (std::size_t i = 0; i < size_; ++i) {

		if (elements_[i] == value) {

			return i;

		}

	}

	return std::nullopt;

}
  • [[nodiscard]]:提醒调用者不要忽略函数的返回值。
  • noexcept:该函数承诺不会抛出异常(exception)。