project0

一. 前置知识

1 函数返回值类型(右值or左值)

2 std::unique_ptr

std::unique_ptr管理对象的析构

1.std::unique_ptr对象析构

2.改变原有std::unique_ptr的管理对象

方法一:

方法二:

可移动不可复制

对上图内容的举例说明:

std::unique_ptr 是 C++11 引入的智能指针之一,用来管理动态分配的对象,确保对象的生命周期由 unique_ptr 来管理,自动释放内存,避免内存泄漏。

  1. 空的 unique_ptr
  • unique_ptr 可以指向一个对象,也可以不指向任何对象,即为空。当 unique_ptr 为空时,它不会占用任何资源。我们可以使用 std::unique_ptr<T> ptr; 来定义一个空的 unique_ptr,它不管理任何资源。

示例:

std::unique_ptr<int> ptr; // 空的 unique_ptr
  • 如果在声明时没有初始化对象,ptr 就是空指针。

非空的 unique_ptr

std::unique_ptr<int> ptr = std::make_unique<int>(10); // ptr 管理一个动态分配的 int 对象
  1. 管理单个对象
  • std::unique_ptr 常用于管理单个对象,例如通过 new 运算符动态分配的对象。当 unique_ptr 被销毁时,它会自动释放对象的内存,避免手动调用 delete

示例:

std::unique_ptr<int> ptr = std::make_unique<int>(10); // 管理一个动态分配的 int 对象
std::cout << *ptr << std::endl;  // 输出 10
  • 这里,std::make_unique<int>(10) 分配了一个 int 类型的对象,且初始化为 10。当 ptr 离开作用域时,它会自动释放内存。
  1. 管理动态分配的对象数组
  • std::unique_ptr 也可以用于管理动态分配的数组。使用 new[] 来分配数组时,我们可以使用 unique_ptr 来管理该数组。

示例:

std::unique_ptr<int[]> arr = std::make_unique<int[]>(5); // 管理动态分配的数组
arr[0] = 10;
arr[1] = 20;
std::cout << arr[0] << ", " << arr[1] << std::endl;  // 输出 10, 20
  • 这里,std::make_unique<int[]>(5) 分配了一个 int 类型的数组,且数组大小为 5。当 arr 离开作用域时,数组的内存会自动释放。
  1. std::unique_ptr 的可移动性
  • std::unique_ptr可移动构造(MoveConstructible)可移动赋值(MoveAssignable)的。意味着可以将一个 unique_ptr 的所有权从一个对象转移到另一个对象,但是不能拷贝它们。

示例:

std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::unique_ptr<int> ptr2 = std::move(ptr1);  // ptr1 的所有权转移给 ptr2
std::cout << *ptr2 << std::endl;  // 输出 10
// ptr1 现在是空的,不能再访问它
  • 在这个例子中,ptr2 获取了 ptr1 管理的对象的所有权,ptr1 成为了空指针。由于 unique_ptr 不允许复制,因此不能使用赋值语句如 ptr2 = ptr1;,只能通过 std::move 进行所有权转移。
  1. 不支持复制构造和复制赋值
  • std::unique_ptr 不允许进行复制构造(CopyConstructible)和复制赋值(CopyAssignable)。这意味着不能将一个 unique_ptr 赋值给另一个 unique_ptr,因为这会导致两个 unique_ptr 同时管理同一个对象,进而导致资源释放时的重复释放(双重释放)。

示例:

std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
// std::unique_ptr<int> ptr2 = ptr1; // 编译错误,不能复制
  • 这里的代码会编译失败,因为 unique_ptr 不支持复制构造和复制赋值。
  1. T 不是合法类型
  • 如果 T 不是合法类型(例如 T 是引用类型),则 std::unique_ptr<T> 是非法的。unique_ptr 只能管理指向对象的指针,而不能管理引用类型或其他不符合要求的类型。

非法示例:

// 编译错误,引用类型不能被 unique_ptr 管理
std::unique_ptr<int&> ptr;  // 错误,不能管理引用
  • 在这个例子中,std::unique_ptr<int&> 无法编译,因为 std::unique_ptr 不能管理引用类型(int& 是引用类型,不是对象类型)。

总结:

  • std::unique_ptr 用于管理动态分配的单个对象或数组。
  • 它支持可移动构造和赋值,但不支持拷贝构造和拷贝赋值。
  • 它在超出作用域时会自动释放资源,从而避免内存泄漏。
  • 不能管理引用类型或其他不合法的类型。

图三:

对上图的解释:

std::unique_ptr 详细解释

std::unique_ptr 是 C++11 引入的智能指针,专门用于管理动态分配的对象(通常通过 newnew[] 创建)。它有以下特点和用途:

  1. conststd::unique_ptr 可以转移所有权
  • std::unique_ptr 是独占的智能指针。只有非 constunique_ptr 能转移它所管理的对象的所有权。当 unique_ptrconst 时,它无法转移所有权,因为它不能修改所管理的对象(转移之后就被置空了,相当于改变了管理的对象)。

  • 示例:

    std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
    std::unique_ptr<int> ptr2 = std::move(ptr1);  // 正常转移所有权
    // std::unique_ptr<int> ptr3 = ptr2;  // 编译错误,不能复制
    
    • 在这个示例中,ptr1 的所有权被 std::move 转移给了 ptr2ptr1 成为一个空指针,无法继续访问其原始对象。

    • 如果 ptr1const,则无法转移所有权

      const std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
      // std::unique_ptr<int> ptr2 = std::move(ptr1);  // 编译错误,不能从 const 的 unique_ptr 转移所有权
      
  1. std::unique_ptr 提供异常安全性
  • std::unique_ptr 确保在正常退出和异常退出时,都能自动释放资源。通过智能指针的生命周期管理,避免了手动调用 delete,从而避免了内存泄漏问题。

  • 示例:

    void processData() {
        std::unique_ptr<int[]> arr = std::make_unique<int[]>(100);
        // 进行操作
        if (someCondition) {
            throw std::runtime_error("Exception occurred");
        }
        // 无论异常是否发生,arr 会在函数结束时自动释放
    }
    
    • 即使在函数中发生了异常,arr 也会在函数返回前自动销毁,确保资源得到释放,提供异常安全性。
  1. 传递和接收 std::unique_ptr 的所有权
  • std::unique_ptr 通过 转移所有权 来传递和接收动态分配对象。它不能被复制,但可以被移动。

  • 示例:

    std::unique_ptr<int> createObject() {
        return std::make_unique<int>(42);  // 返回一个 unique_ptr,该返回值为右值
    }
    
    void processObject(std::unique_ptr<int> ptr) {
        std::cout << *ptr << std::endl;  // 处理传入的 unique_ptr
    }
    
    int main() {
        std::unique_ptr<int> ptr = createObject();
        processObject(std::move(ptr));  // 转移所有权
        // ptr 在这里变为空,不能再使用
    }
    
    • 通过 std::move(ptr)ptr 的所有权被转移到 processObject 函数中,ptr 变为空,不再可以访问所管理的对象。
  1. std::unique_ptr 用作容器元素
  • std::unique_ptr 可以用作容器(例如 std::vector)的元素类型。当需要多态行为或动态分配对象时,std::unique_ptr 是一个常见的选择。

  • 示例:

    class Base {
    public:
        virtual void show() = 0;
    };
    
    class Derived : public Base {
    public:
        void show() override {
            std::cout << "Derived class\n";
        }
    };
    
    int main() {
        std::vector<std::unique_ptr<Base>> vec;
        vec.push_back(std::make_unique<Derived>());
        vec[0]->show();  // 输出 "Derived class"
    }
    
    • 在这个示例中,std::vector<std::unique_ptr<Base>> 用来管理一系列的动态分配对象。当 Derived 对象被销毁时,它会自动调用 Derived 类的析构函数,并释放内存。
  1. std::unique_ptr 与不完整类型(如 pImpl
  • std::unique_ptr 可以用于管理不完整类型(例如 pImpl 手法中的 opaque 指针),从而可以隐藏实现细节。在 pImpl 手法中,通常将实现细节放在一个类的内部,并使用 unique_ptr 来管理它。

  • 示例:

    class MyClassImpl;  // 不完整类型
    
    class MyClass {
    public:
        MyClass();
        ~MyClass();
    private:
        std::unique_ptr<MyClassImpl> impl;  // 管理不完整类型
    };
    
    • 在这个示例中,MyClassImpl 是一个不完整类型,MyClass 通过 std::unique_ptr 管理它的生命周期,隐藏了 MyClassImpl 的实现细节。
    • 使用 std::unique_ptr 管理不完整类型时,删除器会在析构时被调用,要求 MyClassImpl 必须是完整类型。

std::unique_ptr 与继承关系

  • 如果 T 是基类 B 的派生类,那么 std::unique_ptr<T> 可以隐式转换为 std::unique_ptr<B>。这使得 std::unique_ptr 在面向对象设计中非常有用。但必须注意,如果基类的析构函数不是虚拟的,那么删除时可能会出现未定义行为。

  • 示例:

    class Base {
    public:
        virtual ~Base() = default;
    };
    
    class Derived : public Base {
    public:
        ~Derived() override { std::cout << "Derived destructor\n"; }
    };
    
    int main() {
        std::unique_ptr<Base> ptr = std::make_unique<Derived>();
        // `ptr` 会正确调用 Derived 的析构函数
    }
    
    • 在这个示例中,std::unique_ptr<Base> 管理了 Derived 类的对象。当 ptr 被销毁时,Derived 类的析构函数会被调用,并释放内存。

    • 如果基类的析构函数不是虚拟的,那么可能会导致未定义行为:

      class Base {
      public:
          ~Base() { std::cout << "Base destructor\n"; }  // 非虚拟析构函数
      };
      
      class Derived : public Base {
      public:
          ~Derived() override { std::cout << "Derived destructor\n"; }
      };
      
      int main() {
          std::unique_ptr<Base> ptr = std::make_unique<Derived>();
          // 销毁时 Base 的析构函数会被调用,Derived 的析构函数没有被调用
      }
      
      • 这种情况下,std::unique_ptr 只会调用基类 Base 的析构函数,Derived 类的析构函数不会被调用,导致资源没有被正确释放。
  1. 定制删除器和非标准指针类型
  • std::shared_ptr 不同,std::unique_ptr 允许使用定制删除器来管理指针,例如通过 boost::offset_ptr 等自定义类型来管理指向共享内存的对象。

  • 示例:

    std::unique_ptr<int, void(*)(int*)> ptr(new int(10), [](int* p) { delete p; });
    
    • 这个示例中,std::unique_ptr 使用了一个自定义的删除器,通过 Lambda 表达式来删除 int 对象。

总结

  • std::unique_ptr 是一个独占式的智能指针,适用于动态内存管理。它能确保资源在作用域结束时自动释放,提供异常安全性。
  • 它可以通过 std::move 转移所有权,但不支持复制。
  • 它适用于管理基类指针、容器元素以及不完整类型等场景。
  • 使用 std::unique_ptr 时要小心继承关系,确保基类析构函数为虚拟析构函数以避免未定义行为。

例子:

#include <cassert>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <locale>
#include <memory>
#include <stdexcept>
 
// 用于下面运行时多态演示的辅助类
struct B
{
    virtual ~B() = default;
 
    virtual void bar() { std::cout << "B::bar\n"; }
};
 
struct D : B
{
    D() { std::cout << "D::D\n"; }
    ~D() { std::cout << "D::~D\n"; }
 
    void bar() override { std::cout << "D::bar\n"; }
};
 
// 消费 unique_ptr 的函数能以值或以右值引用接收它
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
    p->bar();
    return p;
}
 
// 用于下面自定义删除器演示的辅助函数
void close_file(std::FILE* fp)
{
    std::fclose(fp);
}
 
// 基于 unique_ptr 的链表演示
struct List
{
    struct Node
    {
        int data;
        std::unique_ptr<Node> next;
    };
 
    std::unique_ptr<Node> head;
 
    ~List()
    {
        // 循环按顺序销毁各列表节点,默认析构函数将会递归调用其 “next” 指针的析构函数,
        // 这在足够大的链表上可能造成栈溢出。
        while (head)
        {
            auto next = std::move(head->next);
            head = std::move(next);
        }
    }
 
    void push(int data)
    {
        head = std::unique_ptr<Node>(new Node{data, std::move(head)});
    }
};
 
int main()
{
    std::cout << "1) 独占所有权语义演示\n";
    {
        // 创建一个(独占)资源
        std::unique_ptr<D> p = std::make_unique<D>();
 
        // 转移所有权给 “pass_through”,而它再通过返回值将所有权转移回来
        std::unique_ptr<D> q = pass_through(std::move(p));
 
        // “p” 现在是已被移动的“空”状态,等于 nullptr
        assert(!p);
    }
 
    std::cout << "\n" "2) 运行时多态演示\n";
    {
        // 创建派生类资源并通过基类指向它
        std::unique_ptr<B> p = std::make_unique<D>();
 
        // 动态派发如期工作
        p->bar();
    }
 
    std::cout << "\n" "3) 自定义删除器演示\n";
    std::ofstream("demo.txt") << 'x'; // 准备要读取的文件
    {
        using unique_file_t = std::unique_ptr<std::FILE, decltype(&close_file)>;
        unique_file_t fp(std::fopen("demo.txt", "r"), &close_file);
        if (fp)
            std::cout << char(std::fgetc(fp.get())) << '\n';
    } // 在此调用 “close_file()”(如果 “fp” 为空)
 
    std::cout << "\n" "4) 自定义 lambda 表达式删除器和异常安全性演示\n";
    try
    {
        std::unique_ptr<D, void(*)(D*)> p(new D, [](D* ptr)
        {
            std::cout << "由自定义删除器销毁...\n";
            delete ptr;
        });
 
        throw std::runtime_error(""); // “p” 是普通指针的情况下此处就会泄漏
    }
    catch (const std::exception&)
    {
        std::cout << "捕获到异常\n";
    }
 
    std::cout << "\n" "5) 数组形式的 unique_ptr 演示\n";
    {
        std::unique_ptr<D[]> p(new D[3]);
    } // “D::~D()” 被调用 3 次
 
    std::cout << "\n" "6) 链表演示\n";
    {
        List wall;
        const int enough{1'000'000};
        for (int beer = 0; beer != enough; ++beer)
            wall.push(beer);
 
        std::cout.imbue(std::locale("en_US.UTF-8"));
        std::cout << "墙上有 " << enough << " 瓶啤酒...\n";
    } // 销毁所有啤酒
}

可能的输出:

1) 独占所有权语义演示
D::D
D::bar
D::~D
 
2) 运行时多态演示
D::D
D::bar
D::~D
 
3) 自定义删除器演示
x
 
4) 自定义 lambda 表达式删除器和异常安全性演示
D::D
由自定义删除器销毁...
D::~D
捕获到异常
 
5) 数组形式的 unique_ptr 演示
D::D
D::D
D::D
D::~D
D::~D
D::~D
 
6) 链表演示
墙上有 1,000,000 瓶啤酒...

3 std::unordered_map

map里成员是std::pair<>()






二 实现

加锁, 由于锁不是利用RAII封装的,所以在return前都要释放锁,避免死锁
具体代码不展示了

2 获取Gradescope在线测试用例:

#pragma once
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

void GetTestFileContent() {
  static bool first_enter = true;
  if (first_enter) {
    //  截取gradescope日志输出文件名
    /*
    std::vector<std::string> all_filenames = {
        "/autograder/bustub/test/primer/grading_starter_test.cpp",
        "/autograder/bustub/test/execution/grading_update_executor_test.cpp",
        "/autograder/bustub/test/execution/grading_nested_loop_join_executor_test.cpp",
        "/autograder/bustub/test/execution/grading_limit_executor_test.cpp",
        "/autograder/bustub/test/execution/grading_executor_benchmark_test.cpp",
        "/autograder/bustub/test/concurrency/grading_lock_manager_3_test.cpp",
        "/autograder/bustub/test/buffer/grading_parallel_buffer_pool_manager_test.cpp",
        "/autograder/bustub/test/buffer/grading_lru_replacer_test.cpp",
        "/autograder/bustub/test/execution/grading_executor_integrated_test.cpp",
        "/autograder/bustub/test/execution/grading_sequential_scan_executor_test.cpp",
        "/autograder/bustub/test/concurrency/grading_lock_manager_1_test.cpp",
        "/autograder/bustub/test/execution/grading_distinct_executor_test.cpp",
        "/autograder/bustub/test/buffer/grading_buffer_pool_manager_instance_test.cpp",
        "/autograder/bustub/test/concurrency/grading_lock_manager_2_test.cpp",
        "/autograder/bustub/test/concurrency/grading_transaction_test.cpp",
        "/autograder/bustub/test/buffer/grading_leaderboard_test.cpp",
        "/autograder/bustub/test/container/grading_hash_table_verification_test.cpp",
        "/autograder/bustub/test/concurrency/grading_rollback_test.cpp",
        "/autograder/bustub/test/container/grading_hash_table_concurrent_test.cpp",
        "/autograder/bustub/test/container/grading_hash_table_page_test.cpp",
        "/autograder/bustub/test/concurrency/grading_lock_manager_detection_test.cpp",
        "/autograder/bustub/test/container/grading_hash_table_leaderboard_test.cpp",
        "/autograder/bustub/test/container/grading_hash_table_scale_test.cpp",
        "/autograder/bustub/test/container/grading_hash_table_test.cpp",
        "/autograder/bustub/test/execution/grading_aggregation_executor_test.cpp",
        "/autograder/bustub/test/execution/grading_insert_executor_test.cpp",
        "/autograder/bustub/test/execution/grading_delete_executor_test.cpp",
        "/autograder/bustub/test/execution/grading_hash_join_executor_test.cpp"
        "/autograder/bustub/test/execution/grading_sequential_scan_executor_test.cpp",
        "/autograder/bustub/test/execution/grading_update_executor_test.cpp",
        "/autograder/bustub/test/execution/grading_executor_test_util.h",
        "/autograder/bustub/src/include/execution/plans/mock_scan_plan.h",
        };
    */
    std::vector<std::string> filenames = {
        "/autograder/bustub/test/execution/grading_executor_integrated_test.cpp",
        "/autograder/bustub/test/execution/grading_executor_benchmark_test.cpp",
    };
    std::ifstream fin;
    for (const std::string &filename : filenames) {
      fin.open(filename, std::ios::in);
      if (!fin.is_open()) {
        std::cout << "cannot open the file:" << filename << std::endl;
        continue;
      }
      char buf[200] = {0};
      std::cout << filename << std::endl;
      while (fin.getline(buf, sizeof(buf))) {
        std::cout << buf << std::endl;
      }
      fin.close();
    }
    first_enter = false;
  }
}


使用方法:

  1. 将filenames内的路径更换(具体路径,当你上传一次后就能看到)

  2. 将上述代码插入要上传提交测试的代码中(比如p0_trie.h)

  3. 在任意函数中调用GetTestFileContent(),代码只会执行一次

  4. 可将获取的在线测试用例,放入本地测试文件中,随后可在本地debug

  5. debug时可结合log使用

LOG_INFO("# Pages: %d", num_pages);
LOG_DEBUG("Fetching page %d", page_id);

三 测试结果展示:

本地测试:

去掉测试代码中的 DISABLED_ 前缀

cd build
make starter_trie_test
./test/starter_trie_test

在线测试:

登录 https://www.gradescope.com/ 注册 入口代码:PXWVR5 学校选 Carnegie Mellon University

四 相关资源

posted @ 2025-02-25 20:59  msnthh  阅读(70)  评论(0)    收藏  举报