我会在这个专栏,通过「数据结构与算法」来介绍 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)。