В стиле C-класса массива


Я часто использовать C API в C++, который заставит меня использовать C-массивы в стиле. Мне надоело постоянно использовать динамический вектор с &ВМК[0], так что я написал это c-массивы в стиле контейнера. Пожалуйста, комментарий и дать предложения.

У меня тоже есть один вопрос: моя реализация своп правильно? Меня гораздо больше беспокоит замена двух C-массивы в стиле с разных распределителей, будет ли этот бум на освобождение?

Нынешние "требования" для C-массивы в стиле:

  • Статический размер размер().
  • Неявно преобразуется к т* при необходимости переходить на C функций.
  • Поддержка всех СТД::вектор'ы методов, покуда они совместимы с двумя требованиями ранее.

Это то, что у меня есть:

/*
    Carray - a C++ container that resembles a C-style array very closely
    while supporting high-level methods and dynamic memory allocation.

    Description:
        A Carray is an Random Access Container (http://www.sgi.com/tech/stl/Container.html,
        http://www.sgi.com/tech/stl/RandomAccessContainer.html). The number of elements in
        a Carray is fixed and can not vary after construction. Memory management is automatic.

    Template arguments:
        Carray<typename T, typename A = std::allocator<T> >
        T is the type of each element in the array and A is the allocator
        for the array. The allocator defaults to std::allocator.
        Carray<int, std::allocator<int> >

    Constructors:
        Carray(size_t n) - create new Carray with n uninitialized elements
        Carray(size_t n, value_type newobj) - create new Carray with n elements initialized to newobj
        Carray(const Carray& carray) - copy constructor

        Example:
        Carray<int> arr(50) - Carray of 50 ints - garbage values
        Carray<int> arr(50, int()) - Carray of 50 ints - initialized to default int, 0

    Carray supports all of the associated types, members and methods described on these pages:
        http://www.sgi.com/tech/stl/Container.html
        http://www.sgi.com/tech/stl/ForwardContainer.html
        http://www.sgi.com/tech/stl/ReversibleContainer.html
        http://www.sgi.com/tech/stl/RandomAccessContainer.html
*/

/*
    Copyright 2011 Orson Peters. All rights reserved.

    Redistribution of this work, with or without modification, is permitted if
    Orson Peters is attributed as the original author or licensor of
    this work, but not in any way that suggests that Orson Peters endorses
    you or your use of the work.

    This work is provided by Orson Peters "as is" and any express or implied
    warranties are disclaimed. Orson Peters is not liable for any damage
    arising in any way out of the use of this work.
*/

#ifndef CARRAY_H
#define CARRAY_H

#include <memory> // std::allocator
#include <algorithm> // std::uninitialized_copy
#include <iterator> // std::reverse_iterator
#include <stdexcept> // std::out_of_range
#include <cstddef> // size_t && ptrdiff_t

template<typename T, typename A = std::allocator<T> > class Carray {
public:
    // types
    typedef T value_type;
    typedef A allocator_type;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;

    typedef T* pointer; // for C
    typedef const T* const_pointer; // for C
    typedef T* iterator;
    typedef const T* const_iterator;
    typedef std::reverse_iterator<iterator> reverse_iterator;
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
    typedef T& reference;
    typedef const T& const_reference;

    // contructors
    Carray(size_type size) { // array with N uninitialized elements
        elements = size;
        data = allocator.allocate(size);
    }

    Carray(size_type size, const_reference newobj) { // array with N elements initialized to newobj
        elements = size;
        data = allocator.allocate(size);
        for (iterator it = begin(); it != end(); it++) allocator.construct(it, newobj);
    }

    Carray(const Carray<value_type>& other) { // copy constructor
        elements = other.size();
        data = allocator.allocate(elements);

        std::uninitialized_copy(other.begin(), other.end(), begin());
    } 

    Carray<value_type, allocator_type>& operator=(const Carray<value_type, allocator_type>& other) { // same as copy constructor
        elements = other.size();
        data = allocator.allocate(elements);

        std::uninitialized_copy(other.begin(), other.end(), begin());
    }

    ~Carray() {
        for (iterator it = begin(); it != end(); it++) allocator.destroy(it);
        if (data != 0) {
            allocator.deallocate(data, elements);
            data = 0;
            elements = 0;
        }
    }

    // iterators and references
    reference back() { return data[elements - 1]; }
    const_reference back() const { return data[elements - 1]; }

    reference front() { return data[0]; }
    const_reference front() const { return data[0]; }

    iterator begin() { return iterator(data); }
    const_iterator begin() const { return const_iterator(data); }

    iterator end() { return iterator(data + elements); }
    const_iterator end() const { return const_iterator(data + elements); }

    reverse_iterator rbegin() { return reverse_iterator(end()); }
    const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }

    reverse_iterator rend() { return reverse_iterator(begin()); }
    const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }

    // methods
    bool empty() const { return elements == 0; }
    size_type max_size() const { return allocator.max_size(); }
    size_type size() const { return elements; }
    allocator_type get_allocator() const { return allocator; }
    void swap(Carray<value_type>& other) {
        std::swap(data, other.data);
        std::swap(elements, other.elements);
    }

    // subscripting
    reference at(size_type n) { if (n >= elements) throw std::out_of_range("Carray::at(size_type n)"); return data[n]; }
    const_reference at(size_type n) const { if (n >= elements) throw std::out_of_range("Carray::at(size_type n)"); return data[n]; }

    reference operator[](difference_type n) { return data[n]; } // difference_type to prevent ambiguity with (operator pointer())[n]
    const_reference operator[](difference_type n) const { return data[n]; } // difference_type to prevent ambiguity with (operator pointer())[n]

    operator pointer() { return data; } // implicit conversion to pointer for C support
    operator const_pointer() const { return data; } // implicit conversion to const pointer for C support

private:
    allocator_type allocator;
    pointer data;
    size_type elements;
};

// comparison operators - operator== and operator> are primitives, the rest is derived from them
template<typename T, typename A> bool operator==(const Carray<T,A>& x, const Carray<T,A>& y) {
    return x.size() == y.size() && std::equal(x.begin(), x.end(), y.begin());
}

template<typename T, typename A> bool operator<(const Carray<T,A>& x, const Carray<T,A>& y) {
    return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end());
}

template<typename T, typename A> bool operator>(const Carray<T,A>& x, const Carray<T,A>& y) { return y < x; }
template<typename T, typename A> bool operator!=(const Carray<T,A>& x, const Carray<T,A>& y) { return !(x == y); }
template<typename T, typename A> bool operator>=(const Carray<T,A>& x, const Carray<T,A>& y) { return !(x < y); }
template<typename T, typename A> bool operator<=(const Carray<T,A>& x, const Carray<T,A>& y) { return !(x > y); }

#endif


1096
8
задан 17 мая 2011 в 08:05 Источник Поделиться
Комментарии
2 ответа

Это субъективный момент, но деклараций шаблон контейнеры в стандартной библиотеке не использовать операторы typedef для типа шаблона для остальной части декларации. Например,

template <class T, class Allocator>
class deque {
public:
typedef T value_type;

//...

void push_front( const T& x );
// not void push_front(const value_type& x);

//...
}

Я привык искать шаблон параметр в функции-члена деклараций, и использование типа value_type и т. д. смутило меня.

Лично мне не нравится неявное преобразование к Т*. Вы должны убедиться, что вы думали обо всех тех случаях, когда это может привести к неожиданному поведению, например, в логических контекстах, арифметические операторы и с поток::оператор<<. На мой взгляд лишний вызов функции на преобразование-это небольшая цена, чтобы заплатить для того, чтобы избежать случаев, когда код выглядит правильно, формирует и делает что-то неуловимо не так.

6
ответ дан 18 мая 2011 в 10:05 Источник Поделиться

Ладно, я сделал несколько незначительных изменений:


  1. Изменен конструктор использовать список инициализации:

    Carray(size_type size) 
    : elements(size), data(allocator.allocate(size))
    { }

    Carray(size_type size, const_reference newobj)
    : elements(size), data(allocator.allocate(size))
    { // array with N elements initialized to newobj
    for (iterator it = begin(); it != end(); it++)
    allocator.construct(it, newobj);
    }

    Carray(const Carray<value_type>& other)
    : elements(other.size), data(allocator.allocate(elements))
    { // copy constructor
    std::uninitialized_copy(other.begin(), other.end(), begin());
    }


  2. Переставить члены (вы были инициализации в неправильном порядке):

    allocator_type allocator;
    size_type elements;
    pointer data;

  3. Использовать скопировать и поменять язык на ваш оператор=:

    Carray<value_type, allocator_type>& operator=(const Carray<value_type, allocator_type>& other) { // same as copy constructor
    // This may be a short piece of code, but why not use the copy-and-swap idiom?
    Carray<T, A> temp(other);
    swap(*this, temp);

    return *this;
    }


  4. Сделайте свой своп не бросать гарантия:

    void swap(Carray<value_type>& other) throw()

Я бы также хотел добавить, что ваш бросок() в по телефону() функции не очень описательные. Это, вероятно, не проблема, так как ошибка является очевидной, глядя на функцию, но почему бы не дать это в любом случае более описательное сообщение? Тогда я даже не нужно смотреть на функции, чтобы знать, что пошло не так.

Редактировать

Как Тим Мартин отметил, бросок() - это обычно плохой стиль, и его следует избегать. Я включил его для функции замены из-за двух причин:


  1. Это, как правило, не бросая своп, который является более семантической, а не синтаксической выбор. Это позволяет программисту знать, что своп не должен бросить, но это не помешало.

  2. Цитирую Херб Саттер от исключительных с++ пункт 12:


    Обратите внимание, что своп() поддерживает сильнейших исключением гарантией
    все—а именно, гарантия nothrow; своп() гарантированно не кинут
    исключение при любых обстоятельствах. Получается, что эта функция
    своп() имеет важное значение, стержень в цепочке рассуждений о
    [Контейнер]'s собственное исключение безопасности.

4
ответ дан 18 мая 2011 в 10:05 Источник Поделиться