特洛伊的魔法力量

概念学习C++17

简介

C++ 中的 auto 关键字

C++11 引入的 auto 用于声明由编译器推导类型的变量。编译器根据初始化表达式确定类型,有时能使代码更易读、易维护。

使用示例

下面使用 auto 声明变量:

auto dragon_population{3};          // dragon_population is deduced as an integer
auto westeros{7.7777};              // westeros is deduced as a double
auto wedding_location{"The Twins"}; // wedding_location is deduced as a const char*, not std::string

每个变量的类型都由其初始化值推导。

类型推导

auto 可以减少反复书写复杂类型,使代码更简洁。

const std::vector<std::string> pigeon_pie{"flour", "butter", "pigeon", "salt"};
auto purple_wedding_pie{pigeon_pie};
purple_wedding_pie.emplace_back("the strangler");

此循环中,auto 将 purple_wedding_pie 推导为 std::vector<std::string>,避免再次显式书写该类型。

适用场合

auto 可以与多种 C++ 结构一起使用,是现代 C++ 的常用工具。

auto& element{array[0]};           // reference to an element
const auto object{otherObject};    // const type version of otherObject's type
auto* ptr{&x};                     // pointer to x with the same type as x, but as a pointer.

后续课程中,会经常在 lambda 表达式、范围 for 循环和迭代器相关代码中见到它。

智能指针

智能指针提供自动内存管理,有助于避免原始指针常见的内存泄漏及部分悬空指针问题。它们封装原始指针,并在不再需要所管理资源时自动释放内存。

基本语法

智能指针通常是标准库中的类模板,最常用的是 std::unique_ptr 和 std::shared_ptr。

独占指针

std::unique_ptr 独占所管理对象,保证同一时刻只有一个这样的所有者。拥有资源的 unique_ptr 销毁或重置时,会销毁所管理对象并释放内存。

#include <memory>
// Declaring and defining a unique pointer
auto rightful_king_of_england = std::make_unique<std::string>("Excalibur");

// Unique pointers cannot be copied or assigned
auto mordred = rightful_king_of_england; // Error: Cannot copy a unique_ptr

std::make_unique() 的优点

创建 unique_ptr 时,通常优先使用 std::make_unique(),而不是直接 new:

  1. 异常安全:对象构造抛出异常时,已分配的内存会被正确释放,避免泄漏。
  2. 清晰简洁:集中表达资源创建与所有权,不必重复书写原始指针管理代码;对象类型通过模板参数指定,构造参数可转发。
  3. 优化机会:这种明确的创建方式便于编译器分析与优化,具体效果取决于实现。
  4. 避免误用:手工管理同一原始指针时,可能提前删除资源,导致 unique_ptr 离开作用域时重复释放,产生未定义行为。

共享指针

std::shared_ptr 允许多个对象共同拥有同一资源。它记录共享所有者数量,只有最后一个所有者销毁或重置时,才释放资源。

// Declaring and defining a shared pointer to a dynamically allocated string
auto martian_congressional_republic = std::make_shared<std::string>("protomolecule");

// Creating more shared pointer that shares ownership
auto outer_planets_alliance = martian_congressional_republic;
auto united_nations = martian_congressional_republic;

注意:数组与标准版本 C++17 及更早版本不支持直接使用 std::make_shared<T[]> 创建数组;该形式从 C++20 才提供。对象与数组的释放方式不同,处理数组时不能误用单对象删除方式。C++17 中可优先考虑 std::vector,或使用符合标准的数组专用管理方式及必要的自定义删除器。务必确认编译器与标准库支持情况。

std::make_shared() 的优点

与 make_unique 类似,make_shared 有助于提高可读性和异常安全性。它通常把控制块与对象合并分配,减少分配次数与内存管理开销,降低资源管理出错的风险。对象类型由模板参数指定,构造参数得到转发,代码也更加清晰。使用它通常能使共享资源管理更安全、简洁。

使用建议

管理拥有所有权的资源时,优先考虑智能指针:独占资源使用 unique_ptr,确实需要共享所有权时使用 shared_ptr。原始指针适合表达非拥有关系,或对接旧代码。多数独占所有权场景使用 unique_ptr 即可,它不需要引用计数开销;shared_ptr 会增加开销和复杂度,应按实际共享需求使用。

任务说明

来到充满奇迹与魔法的特洛伊世界。这里许多人拥有异能,日常用它改变环境、影响动物或其他人。魔法还会凝聚为独一无二的神器,受到冒险者、工匠与学者追捧。

本练习需要为特洛伊人、他们拥有的神器及异能之间的作用编写模型。

你需要完成六个任务:首先创建人物,再处理神器与异能。

1. 为特洛伊世界创建人物

人物是模型的核心。他们可以拥有独一无二的神器,也可以觉醒异能。异能能够影响他人,因此还要记录人物是否受其他异能影响。

已经提供 artifact 和 power 结构体。实现 human 结构体或类,包含名为 possession、指向 artifact 的智能指针。同一时刻,每件神器只能属于一人。

human 还需要两个成员:保存自身异能的 own_power,以及保存所受异能的 influenced_by。二者都是指向 power 的智能指针。同一种异能可能由一人施展,同时影响其他人。

默认情况下,人物出生时没有神器、没有自身异能,也不受其他异能影响。

human mindy_mccready{};
mindy_mccready.possession;
// => nullptr
mindy_mccready.own_power;
// => nullptr
mindy_mccready.influenced_by;
// => nullptr

2. 创造神器

只有静态对象的模型有些无趣。现在创建独特神器,并交给指定人物。

定义 give_new_artifact,接收 human 引用和字符串,以字符串创建新的 artifact,并设置该人物的 possession。函数不返回值。

human erik_magnus_lehnsherr{};
give_new_artifact(erik_magnus_lehnsherr, "Mind shielding helmet");

erik_magnus_lehnsherr.possession->name;
// "Mind shielding helmet"

3. 让神器可以交换

特洛伊世界充满互动,人物应能通过交换所有物来交易。

编写 exchange_artifacts,接收两个神器智能指针并交换内容,不返回值。unique_ptr 不能复制,包括作为按值参数传递,因此这里应使用其引用。

human uchiha{};
give_new_artifact(uchiha, "konoha headband");
human uzumaki{};
give_new_artifact(uzumaki, "forehead protector");

exchange_artifacts(uchiha.possession, uzumaki.possession);

uchiha.possession->name;
// "forehead protector"
uzumaki.possession->name;
// "konoha headband"

4. 赋予人物异能

特洛伊最令人兴奋的是各种异能:有人能用意念熔化铁,有人在夜间能瞬间治愈伤口。

定义 manifest_power,接收人物引用与字符串,以字符串创建 power,并设置其 own_power。函数不返回值。

human eleven {};
manifest_power(eleven, "psychokinesis");

eleven.own_power->effect;
// "psychokinesis"

5. 使用异能

再强大的异能也需要实际使用。模型以人物为中心,因此要追踪异能对人物的影响。

编写返回 void 的 use_power,接收两个人物引用,第一个为施法者,第二个为目标。让目标的 influenced_by 指向施法者的异能。

为简化问题,一个人物同一时刻只能受一种异能影响。即使施法者不复存在,这种影响也仍需保留。

human pamela_isley{};
manifest_power(pamela_isley, "control pheromones");

human count_vertigo{};
use_power(pamela_isley, count_vertigo);
count_vertigo.influenced_by->effect;
// "control pheromones"

6. 追踪异能强度

某些异能施加给多人后会减弱,或触发特殊效果,因此需要统计与每种异能关联的人数。

定义 power_intensity,接收人物,返回 int 类型的异能强度。若人物没有异能,返回 0;否则强度应反映施法者及当前所有受影响的人。

human jean_grey{};
manifest_power(jean_grey, "uplifting personality");

human scott{};
human logan{};
human ororo{};

use_power(jean_grey, ororo);
use_power(jean_grey, logan);
use_power(jean_grey, scott);

power_intensity(jean_grey);
// 4
需要一点帮助?查看分步提示

提示

1. 创建人物

  • 本练习重点是 unique_ptr 与 shared_ptr:各个 human 成员适合哪一种?
  • 神器不共享,异能则需要追踪多个受影响者。
  • possession 应独占,异能指针应共享。

2. 创造神器

  • 创建智能指针并赋给 possession。
  • 通过引用修改函数外的对象。
  • 参考 std::make_unique。
  • 模板尖括号内指定目标类型,圆括号内提供构造对象所需参数。
  • 完整示例:variable = std::make_unique<int>(int{23});。

3. 交换神器

4. 赋予异能

  • 创建智能指针并赋给 own_power。
  • 通过引用修改函数外对象。
  • 参考 std::make_shared。
  • 模板参数指定类型,圆括号内传入构造参数。
  • 完整示例:variable = std::make_shared<int>(int{23});。

5. 使用异能

  • 通过引用修改函数外的对象。

6. 追踪强度