Viết Vùng chứa STL của riêng bạn


120

Có hướng dẫn về cách người ta nên viết vùng chứa mới sẽ hoạt động giống như bất kỳ vùng STLchứa nào không?


7
Xem triển khai của các vùng chứa tiêu chuẩn hiện có và cố gắng hiểu chúng - các chức năng, kiểu trả về, quá tải toán tử, kiểu lồng nhau, quản lý bộ nhớ và tất cả.
Nawaz 13/10/11

Tôi thường bắt đầu bằng cách sao chép các nguyên mẫu hàm thành viên của bất kỳ vùng chứa nào gần nhất trong khái niệm với những gì tôi đang làm, từ msdn hoặc tiêu chuẩn. ( Cplusplus.com không có ++ 11 chức năng C, và www.sgi.com không phù hợp)
Mooing Duck

@Mooing Duck: bạn nghĩ msdn gần với tiêu chuẩn hơn sgi?
Dani

3
Nó chắc chắn là như vậy. MSDN là hiện tại - SGI là tiền tiêu chuẩn
Puppy

9
Tham khảo trực tuyến tốt nhất (tính đầy đủ của wrt, tính đúng đắn và đặc biệt là khả năng sử dụng) cho đến nay là cppreference.com. Nó cũng giải thích rất nhiều tính năng ngôn ngữ ngoài thư viện. Và đó là wiki, vì vậy nó sẽ ít lỗi hơn cplusplus.com.
rubenvb

Câu trả lời:


209

Dưới đây là một chuỗi pseudo-container Tôi chắp ghép từ § 23.2.1 \ 4 Lưu ý rằng iterator_categorynên là một trong std::input_iterator_tag, std::output_iterator_tag, std::forward_iterator_tag, std::bidirectional_iterator_tag, std::random_access_iterator_tag. Cũng lưu ý rằng bên dưới về mặt kỹ thuật nghiêm ngặt hơn so với yêu cầu, nhưng đây là ý tưởng. Lưu ý rằng phần lớn các hàm "tiêu chuẩn" là tùy chọn về mặt kỹ thuật, do sự tuyệt vời của nó là các trình vòng lặp.

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

Ngoài ra, bất cứ khi nào tôi tạo một vùng chứa, tôi kiểm tra với một lớp ít nhiều như thế này:

#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;

Tạo các hộp chứa các testerđối tượng và gọi từng đối tượng function()khi bạn kiểm tra vùng chứa của mình. Không tạo bất kỳ testerđối tượng toàn cục nào . Nếu container của bạn gian lận ở bất cứ đâu, testerlớp này sẽ assertvà bạn sẽ biết rằng bạn đã vô tình gian lận ở đâu đó.


1
Hay đấy. Trình thử nghiệm của bạn hoạt động như thế nào? Có một số lỗi phân tích cú pháp, rất nhỏ (thiếu ';') nhưng không chắc chắn cách thức hoạt động của trình hủy xác minh. Ồ, ý bạn là assert(tester::livecount == 0);. Mmmmm, vẫn không chắc cách thức hoạt động của khung trình kiểm tra này. Bạn có thể cho một ví dụ?
Adrian

2
Trình kiểm tra có một thành viên không ổn định duy nhất là một con trỏ đến chính nó và trình hủy và các thành viên là một cách để kiểm tra rằng không có bất kỳ sự hợp lệ nào memcpyxảy ra. (thử nghiệm không dễ dàng, nhưng nó bắt được một số). Đây livecountlà một công cụ phát hiện rò rỉ đơn giản, để đảm bảo rằng vùng chứa của bạn được gọi là số lượng hàm tạo và hủy bằng nhau.
Mooing Duck

Ok, tôi hiểu rồi, nhưng điều đó kiểm tra trình lặp của bạn như thế nào? BTW, tôi nghĩ ý bạn là verifierkhông varifier.
Adrian

4
@Adrian Không không, bạn viết vùng chứa của mình, sau đó đặt một loạt những thứ này vào vùng chứa và thực hiện các thao tác với vùng chứa, để xác minh rằng bạn không vô tình ghi nhớ và ghi nhớ để gọi tất cả các trình hủy.
Mooing Duck

1
tôi có thể gợi ý kế thừa trình lặp từ std::iteratortiêu đề không<iterator>
sp2danny 13/12/15

28

Bạn sẽ cần đọc phần Tiêu chuẩn C ++ về Vùng chứa và các yêu cầu mà Tiêu chuẩn C ++ áp đặt cho việc triển khai vùng chứa.

Chương liên quan trong tiêu chuẩn C ++ 03 là:

Phần 23.1 Yêu cầu về container

Chương liên quan trong tiêu chuẩn C ++ 11 là:

Phần 23.2 Yêu cầu về container

Bản nháp gần nhất của tiêu chuẩn C ++ 11 có sẵn miễn phí tại đây .

Bạn cũng có thể đọc một số cuốn sách xuất sắc sẽ giúp bạn hiểu các yêu cầu từ góc độ của người sử dụng vùng chứa. Hai cuốn sách xuất sắc khiến tôi dễ dàng nhận ra là:

STL hiệu quả củaScott Meyers &
Thư viện tiêu chuẩn C ++: Hướng dẫn và tài liệu tham khảo củaNicolai Josutils


6

Đây là một triển khai rất đơn giản của một vectơ giả, về cơ bản là một trình bao bọc xung quanh std::vectorvà có trình vòng lặp (nhưng thực) của riêng nó, bắt chước trình vòng lặp STL. Một lần nữa, trình lặp rất đơn giản, bỏ qua nhiều khái niệm nhưconst_iterator , kiểm tra tính hợp lệ, v.v.

Mã có thể chạy ra khỏi hộp.

#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
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.