手动实现一个vec

119 阅读4分钟

前面我们已经实现了Vec的迭代器功能,可以循环访问Vec中的元素。但是你们有没有发现有一个有意思的情况了?那就是我们把初始化缓存和释放内存的逻辑在 Vec 和 IntoIter 里面一模一样地写了两次。现在我们已经实现了功能,而且发现了逻辑的重复,是时候对代码做一些压缩了。因而我们就要抽象出(ptr,cap)并赋予他们分配,扩容释放的逻辑。

RawVec

struct RawVec<T> {
    ptr: Unique<T>,
    cap: usize,
}

impl<T> RawVec<T> {
    fn new() -> Self {
        assert!(mem::size_of::<T>() != 0"TODO:实现零尺寸类型的支持");
        RawVec { ptr: Unique::empty(), cap: 0 }
    }

    // 与Vec一样
    fn grow(&mut self) {
        unsafe {
            let align = mem::align_of::<T>();
            let elem_size = mem::size_of::<T>();

            let (new_cap, ptr) = if self.cap == 0 {
                let ptr = heap::allocate(elem_size, align);
                (1, ptr)
            } else {
                let new_cap = 2 * self.cap;
                let ptr = heap::reallocate(self.ptr.as_ptr() as *mut _,
                                            self.cap * elem_size,
                                            new_cap * elem_size,
                                            align);
                (new_cap, ptr)
            };

            // 如果分配或再分配失败,我们会得到null
            if ptr.is_null() { oom() }

            self.ptr = Unique::new(ptr as *mut _);
            self.cap = new_cap;
        }
    }
}

impl<T> Drop for RawVec<T> {
    fn drop(&mut self) {
        if self.cap != 0 {
            let align = mem::align_of::<T>();
            let elem_size = mem::size_of::<T>();
            let num_bytes = elem_size * self.cap;
            unsafe {
                heap::deallocate(self.ptr.as_mut() as *mut _, num_bytes, align);
            }
        }
    }
}

我们需要改造下我们之前的Vec的实现了:

pub struct Vec<T> {
    buf: RawVec<T>,
    len: usize,
}

impl<T> Vec<T> {
    fn ptr(&self-> *mut T { self.buf.ptr.as_ptr() }

    fn cap(&self-> usize { self.buf.cap }

    pub fn new() -> Self {
        Vec { buf: RawVec::new(), len: 0 }
    }

    // push/pop/insert/remove基本没变,只改变了:
    // self.ptr -> self.ptr()
    // self.cap -> self.cap()
    // self.grow -> self.buf.grow()
}

impl<T> Drop for Vec<T> {
    fn drop(&mut self) {
        while let Some(_) = self.pop() {}
        // 释放空间由RawVec负责
    }
}

然后我们简化迭代器:

struct IntoIter<T> {
    _buf: RawVec<T>, // 我们并不关心这个,只是需要它们保持分配空间不被销毁
    start: *const T,
    end: *const T,
}

// next和next_back保持不变,因为它们并没有用到buf

impl<T> Drop for IntoIter<T> {
    fn drop(&mut self) {
        // 只需要保证所有的元素都被读到了
        // 缓存会在随后自己清理自己
        for _ in &mut *self {}
    }
}

impl<T> Vec<T> {
    pub fn into_iter(self-> IntoIter<T> {
        unsafe {
            // 需要使用ptr::read非安全地把buf移出,因为它不是Copy,
            // 而且Vec实现了Drop(所以我们不能销毁它)
            let buf = ptr::read(&self.buf);
            let len = self.len;
            mem::forget(self);

            IntoIter {
                start: *buf.ptr,
                end: buf.ptr.offset(len as isize),
                _buf: buf,
            }
        }
    }
}

Drain

这样就看起来好多了嘛!!!

你以为这就结束了吗,不不,精彩的还在后面让我们继续探索Drain对Vec的影响,本质上和迭代器一样都是迭代Vec中的元素,但是与迭代器不同的是它并不获取 Vec 的值,而是借用 Vec 并且不改变它的分配空间。

不要逼逼,展示代码:

use std::marker::PhantomData;

struct Drain<'a, T: 'a> {
    // 这里需要限制生命周期。我们使用&'a mut Vec<T>,因为这就是语义上我们包含的东西。
    // 我们只调用pop()和remove(0)
    vec: PhantomData<&'a mut Vec<T>>,
    start: *const T,
    end: *const T,
}

impl<'a, T> Iterator for Drain<'a, T> {
    type Item = T;
    fn next(&mut self-> Option<T> {
        if self.start == self.end {
            None

等一下,难道你眼瞎嘛,IntoIter 和 Drain 有着完全一样的结构,那不得提取出来,本着代码优雅,复用原则也得整一波不是。再看抽象后的代码:

struct RawValIter<T> {
    start: *const T,
    end: *const T,
}

impl<T> RawValIter<T> {
    // 构建它是非安全的,因为它没有关联的生命周期。
    unsafe fn new(slice: &[T]) -> Self {
        RawValIter {
            start: slice.as_ptr(),
            end: if slice.len() == 0 {
                // 如果len == 0,说明没有真的分配内存。这时需要避免offset,
                // 因为那会给LLVM的GEP提供错误的信息
                slice.as_ptr()
            } else {
                slice.as_ptr().offset(slice.len() as isize)
            }
        }
    }
}

// Iterator和DoubleEndedIterator的实现与IntoIter完全一样。

这样就感觉舒坦多了,允许你自我陶醉一会,接着我们要把IntoIter 改造下,代码如下:

pub struct IntoIter<T> {
    _buf: RawVec<T>, // 我们并不关心这个,只是需要它们保持分配空间不被销毁
    iter: RawValIter<T>,
}

impl<T> Iterator for IntoIter<T> {
    type Item = T;
    fn next(&mut self-> Option<T> { self.iter.next() }
    fn size_hint(&self-> (usizeOption<usize>) { self.iter.size_hint() }
}

impl<T> DoubleEndedIterator for IntoIter<T> {
    fn next_back(&mut self-> Option<T> { self.iter.next_back() }
}

impl<T> Drop for IntoIter<T> {
    fn drop(&mut self) {
        for _ in &mut self.iter {}
    }
}

impl<T> Vec<T> {
    pub fn into_iter(self-> IntoIter<T> {
        unsafe {
            let iter = RawValIter::new(&self);

            let buf = ptr::read(&self.buf);
            mem::forget(self);

            IntoIter {
                iter: iter,
                _buf: buf,
            }
        }
    }
}

到了这里Drain不就变得很简单了嘛,各位大哥请看下面代码:

use std::marker::PhantomData;

pub struct Drain<'a, T: 'a> {
    vec: PhantomData<&'a mut Vec<T>>,
    iter: RawValIter<T>,
}

impl<'a, T> Iterator for Drain<'a, T> {
    type Item = T;
    fn next(&mut self-> Option<T> { self.iter.next() }
    fn size_hint(&self-> (usizeOption<usize>) { self.iter.size_hint() }
}

impl<'a, T> DoubleEndedIterator for Drain<'a, T> {
    fn next_back(&mut self-> Option<T> { self.iter.next_back() }
}

impl<'a, T> Drop for Drain<'a, T> {
    fn drop(&mut self) {
        for _ in &mut self.iter {}
    }
}

impl<T> Vec<T> {
    pub fn drain(&mut self-> Drain<T> {
        unsafe {
            let iter = RawValIter::new(&self);

            // 这一步是为了mem::forget的安全。如果Drain被forget,我们会泄露整个Vec的内容
            // 同时,既然我们无论如何都会做这一步,为什么不现在做呢?
            self.len = 0;

            Drain {
                iter: iter,
                vec: PhantomData,
            }
        }
    }
}

好了,各位大哥我们对Vec的进一步优化就先到这里,烦请各位大哥给小弟一个关注,小弟我不胜感激。

扫码_搜索联合传播样式-标准色版.png