การเขียน STL Container ของคุณเอง


120

มีแนวทางว่าควรเขียนคอนเทนเนอร์ใหม่ซึ่งจะมีพฤติกรรมเหมือนSTLคอนเทนเนอร์ใดบ้างหรือไม่?


7
ดูการใช้งานของคอนเทนเนอร์มาตรฐานที่มีอยู่และพยายามทำความเข้าใจ - ฟังก์ชันประเภทการส่งคืนตัวดำเนินการโอเวอร์โหลดประเภทที่ซ้อนกันการจัดการหน่วยความจำและทั้งหมด
Nawaz

ฉันมักจะเริ่มต้นด้วยการคัดลอกต้นแบบฟังก์ชันสมาชิกของคอนเทนเนอร์ใดก็ตามที่ใกล้เคียงที่สุดกับแนวคิดที่ฉันกำลังทำไม่ว่าจะจาก msdn หรือมาตรฐาน ( cplusplus.comไม่มีฟังก์ชัน C ++ 11 และ www.sgi.com ไม่ตรงกัน)
Mooing Duck

@Mooing Duck: คุณคิดว่า msdn ใกล้เคียงกับมาตรฐานมากกว่า sgi หรือไม่?
Dani

3
เป็นอย่างแน่นอน MSDN เป็นปัจจุบัน - SGI เป็นมาตรฐานล่วงหน้า
Puppy

9
ข้อมูลอ้างอิงออนไลน์ที่ดีที่สุด (ความสมบูรณ์ความถูกต้องและความสามารถในการใช้งานโดยเฉพาะอย่างยิ่ง) อยู่ไกลจาก cppreference.com นอกจากนี้ยังอธิบายคุณลักษณะภาษามากมายนอกเหนือจากห้องสมุด และเป็นวิกิดังนั้นจึงควรมีข้อผิดพลาดน้อยกว่า cplusplus.com
rubenvb

คำตอบ:


209

นี่คือลำดับที่ฉันหลอกภาชนะปะติดปะต่อจาก§ 23.2.1 \ 4 หมายเหตุว่าiterator_categoryควรจะเป็นหนึ่งstd::input_iterator_tag, std::output_iterator_tag, std::forward_iterator_tag, ,std::bidirectional_iterator_tag std::random_access_iterator_tagโปรดทราบว่าด้านล่างมีความเข้มงวดทางเทคนิคมากกว่าที่กำหนด แต่นี่เป็นแนวคิด โปรดทราบว่าฟังก์ชัน "มาตรฐาน" ส่วนใหญ่เป็นทางเลือกในทางเทคนิคเนื่องจากความยอดเยี่ยมที่เป็นตัวทำซ้ำ

template <class T, class A = std::allocator<T> >
class X {
public:
    typedef A allocator_type;
    typedef typename A::value_type value_type; 
    typedef typename A::reference reference;
    typedef typename A::const_reference const_reference;
    typedef typename A::difference_type difference_type;
    typedef typename A::size_type size_type;

    class iterator { 
    public:
        typedef typename A::difference_type difference_type;
        typedef typename A::value_type value_type;
        typedef typename A::reference reference;
        typedef typename A::pointer pointer;
        typedef std::random_access_iterator_tag iterator_category; //or another tag

        iterator();
        iterator(const iterator&);
        ~iterator();

        iterator& operator=(const iterator&);
        bool operator==(const iterator&) const;
        bool operator!=(const iterator&) const;
        bool operator<(const iterator&) const; //optional
        bool operator>(const iterator&) const; //optional
        bool operator<=(const iterator&) const; //optional
        bool operator>=(const iterator&) const; //optional

        iterator& operator++();
        iterator operator++(int); //optional
        iterator& operator--(); //optional
        iterator operator--(int); //optional
        iterator& operator+=(size_type); //optional
        iterator operator+(size_type) const; //optional
        friend iterator operator+(size_type, const iterator&); //optional
        iterator& operator-=(size_type); //optional            
        iterator operator-(size_type) const; //optional
        difference_type operator-(iterator) const; //optional

        reference operator*() const;
        pointer operator->() const;
        reference operator[](size_type) const; //optional
    };
    class const_iterator {
    public:
        typedef typename A::difference_type difference_type;
        typedef typename A::value_type value_type;
        typedef typename const A::reference reference;
        typedef typename const A::pointer pointer;
        typedef std::random_access_iterator_tag iterator_category; //or another tag

        const_iterator ();
        const_iterator (const const_iterator&);
        const_iterator (const iterator&);
        ~const_iterator();

        const_iterator& operator=(const const_iterator&);
        bool operator==(const const_iterator&) const;
        bool operator!=(const const_iterator&) const;
        bool operator<(const const_iterator&) const; //optional
        bool operator>(const const_iterator&) const; //optional
        bool operator<=(const const_iterator&) const; //optional
        bool operator>=(const const_iterator&) const; //optional

        const_iterator& operator++();
        const_iterator operator++(int); //optional
        const_iterator& operator--(); //optional
        const_iterator operator--(int); //optional
        const_iterator& operator+=(size_type); //optional
        const_iterator operator+(size_type) const; //optional
        friend const_iterator operator+(size_type, const const_iterator&); //optional
        const_iterator& operator-=(size_type); //optional            
        const_iterator operator-(size_type) const; //optional
        difference_type operator-(const_iterator) const; //optional

        reference operator*() const;
        pointer operator->() const;
        reference operator[](size_type) const; //optional
    };

    typedef std::reverse_iterator<iterator> reverse_iterator; //optional
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator; //optional

    X();
    X(const X&);
    ~X();

    X& operator=(const X&);
    bool operator==(const X&) const;
    bool operator!=(const X&) const;
    bool operator<(const X&) const; //optional
    bool operator>(const X&) const; //optional
    bool operator<=(const X&) const; //optional
    bool operator>=(const X&) const; //optional

    iterator begin();
    const_iterator begin() const;
    const_iterator cbegin() const;
    iterator end();
    const_iterator end() const;
    const_iterator cend() const;
    reverse_iterator rbegin(); //optional
    const_reverse_iterator rbegin() const; //optional
    const_reverse_iterator crbegin() const; //optional
    reverse_iterator rend(); //optional
    const_reverse_iterator rend() const; //optional
    const_reverse_iterator crend() const; //optional

    reference front(); //optional
    const_reference front() const; //optional
    reference back(); //optional
    const_reference back() const; //optional
    template<class ...Args>
    void emplace_front(Args&&...); //optional
    template<class ...Args>
    void emplace_back(Args&&...); //optional
    void push_front(const T&); //optional
    void push_front(T&&); //optional
    void push_back(const T&); //optional
    void push_back(T&&); //optional
    void pop_front(); //optional
    void pop_back(); //optional
    reference operator[](size_type); //optional
    const_reference operator[](size_type) const; //optional
    reference at(size_type); //optional
    const_reference at(size_type) const; //optional

    template<class ...Args>
    iterator emplace(const_iterator, Args&&...); //optional
    iterator insert(const_iterator, const T&); //optional
    iterator insert(const_iterator, T&&); //optional
    iterator insert(const_iterator, size_type, T&); //optional
    template<class iter>
    iterator insert(const_iterator, iter, iter); //optional
    iterator insert(const_iterator, std::initializer_list<T>); //optional
    iterator erase(const_iterator); //optional
    iterator erase(const_iterator, const_iterator); //optional
    void clear(); //optional
    template<class iter>
    void assign(iter, iter); //optional
    void assign(std::initializer_list<T>); //optional
    void assign(size_type, const T&); //optional

    void swap(X&);
    size_type size() const;
    size_type max_size() const;
    bool empty() const;

    A get_allocator() const; //optional
};
template <class T, class A = std::allocator<T> >
void swap(X<T,A>&, X<T,A>&); //optional

นอกจากนี้เมื่อใดก็ตามที่ฉันสร้างคอนเทนเนอร์ฉันจะทดสอบกับชั้นเรียนเช่นนี้มากหรือน้อย:

#include <cassert>
struct verify;
class tester {
    friend verify;
    static int livecount;
    const tester* self;
public:
    tester() :self(this) {++livecount;}
    tester(const tester&) :self(this) {++livecount;}
    ~tester() {assert(self==this);--livecount;}
    tester& operator=(const tester& b) {
        assert(self==this && b.self == &b);
        return *this;
    }
    void cfunction() const {assert(self==this);}
    void mfunction() {assert(self==this);}
};
int tester::livecount=0;
struct verify {
    ~verify() {assert(tester::livecount==0);}
}verifier;

สร้างคอนเทนเนอร์ของtesterวัตถุและเรียกแต่ละคนfunction()เมื่อคุณทดสอบคอนเทนเนอร์ของคุณ อย่าสร้างtesterวัตถุส่วนกลางใด ๆ ถ้าคอนเทนเนอร์ของคุณโกงที่ไหนtesterคลาสนี้assertจะรู้ว่าคุณโกงที่ไหนโดยไม่ได้ตั้งใจ


1
นี่เป็นเรื่องที่น่าสนใจ ผู้ทดสอบของคุณทำงานอย่างไร มีข้อผิดพลาดในการแยกวิเคราะห์หลายประการซึ่งเป็นเรื่องเล็กน้อย (ไม่มี ';') แต่ไม่แน่ใจว่าการตรวจสอบตัวทำลายทำงานอย่างไร assert(tester::livecount == 0);โอ้คุณหมาย อืมยังไม่แน่ใจว่าเฟรมเวิร์กทดสอบนี้ทำงานอย่างไร ช่วยยกตัวอย่างได้ไหม
Adrian

2
ผู้ทดสอบมีสมาชิกที่ไม่หยุดนิ่งเพียงตัวเดียวที่เป็นตัวชี้ตัวเองและตัวทำลายและสมาชิกเป็นวิธีตรวจสอบว่าไม่มีสิ่งที่ไม่ถูกต้องmemcpyเกิดขึ้น (การทดสอบไม่สามารถเข้าใจผิดได้ แต่ก็จับได้บางส่วน) นี่livecountคือเครื่องตรวจจับการรั่วไหลอย่างง่ายเพื่อให้แน่ใจว่าภาชนะของคุณเรียกตัวสร้างและตัวทำลายจำนวนเท่ากัน
หมูปิ้ง

ตกลงฉันเห็นแล้ว แต่จะทดสอบตัววนซ้ำของคุณอย่างไร BTW ผมคิดว่าคุณหมายถึงไม่ได้verifier varifier
Adrian

4
@ เอเดรียนไม่ไม่คุณเขียนคอนเทนเนอร์ของคุณแล้วใส่พวงของสิ่งเหล่านี้ลงในคอนเทนเนอร์และทำสิ่งต่างๆกับคอนเทนเนอร์เพื่อยืนยันว่าคุณไม่ได้ตั้งใจ memcpy และจำไว้ว่าจะเรียกผู้ทำลายทั้งหมด
หมูปิ้ง

1
ฉันขอแนะนำให้สืบทอด iterator จากstd::iteratorส่วนหัว<iterator>
sp2danny

28

คุณจะต้องอ่านส่วนมาตรฐาน C ++ เกี่ยวกับคอนเทนเนอร์และข้อกำหนดที่มาตรฐาน C ++ กำหนดสำหรับการใช้งานคอนเทนเนอร์

บทที่เกี่ยวข้องในมาตรฐาน C ++ 03 คือ:

ส่วนที่ 23.1 ข้อกำหนดเกี่ยวกับคอนเทนเนอร์

บทที่เกี่ยวข้องในมาตรฐาน C ++ 11 คือ:

ส่วนที่ 23.2 ข้อกำหนดเกี่ยวกับคอนเทนเนอร์

แบบร่างใกล้สุดท้ายของมาตรฐาน C ++ 11 มีให้อ่านฟรีที่นี่ที่นี่

คุณอาจอ่านหนังสือที่ยอดเยี่ยมซึ่งจะช่วยให้คุณเข้าใจข้อกำหนดจากมุมมองของผู้ใช้คอนเทนเนอร์ หนังสือที่ยอดเยี่ยมสองเล่มที่ทำให้ฉันนึกถึงอย่างง่ายดาย ได้แก่ :

STL ที่มีประสิทธิภาพโดยScott Meyers &
The C ++ Standard Library: บทช่วยสอนและข้อมูลอ้างอิงโดยNicolai Josutils


6

นี่คือการนำเวกเตอร์ปลอมไปใช้งานที่ง่ายมากซึ่งโดยพื้นฐานแล้วจะเป็นกระดาษห่อหุ้มรอบ ๆstd::vectorและมีตัววนซ้ำ (แต่เป็นของจริง) ของตัวเองซึ่งเลียนแบบตัวทำซ้ำ STL อีกครั้งตัววนซ้ำนั้นง่ายมากโดยข้ามแนวคิดหลายอย่างเช่นการconst_iteratorตรวจสอบความถูกต้องเป็นต้น

โค้ดสามารถรันได้จากกล่อง

#include <iostream>
#include <string>
#include <vector>

template<typename T>
struct It
{
    std::vector<T>& vec_;
    int pointer_;

    It(std::vector<T>& vec) : vec_{vec}, pointer_{0} {}

    It(std::vector<T>& vec, int size) : vec_{vec}, pointer_{size} {}

    bool operator!=(const It<T>& other) const
    {
        return !(*this == other);
    }

    bool operator==(const It<T>& other) const
    {
        return pointer_ == other.pointer_;
    }

    It& operator++()
    {
        ++pointer_;            
        return *this;
    }

    T& operator*() const
    {
        return vec_.at(pointer_);   
    }
};

template<typename T>
struct Vector
{
    std::vector<T> vec_;

    void push_back(T item)
    {
        vec_.push_back(item);
    };

    It<T> begin()
    {
        return It<T>(vec_);
    }

    It<T> end()
    {
        return It<T>(vec_, vec_.size());
    }
};

int main()
{
  Vector<int> vec;
  vec.push_back(1);
  vec.push_back(2);
  vec.push_back(3);

  bool first = true;
  for (It<int> it = vec.begin(); it != vec.end(); ++it)
  {
      if (first) //modify container once while iterating
      {
          vec.push_back(4);
          first = false;
      }

      std::cout << *it << '\n'; //print it 
      (*it)++;                  //change it
  }

  for (It<int> it = vec.begin(); it != vec.end(); ++it)
  {
      std::cout << *it << '\n'; //should see changed value
  }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.