I often use C API's in C++ that force me to use C-style arrays. I got sick of constantly using a dynamic vector with &vec[0]
, so I wrote this C-style array container. Please review and give suggestions.
I also have one question: is my implementation of swap
correct? My biggest worry is swapping two C-style arrays with different allocators, will this go boom on the deallocation?
The current "demands" for the C-style array:
- Static size, so no
resize()
. - Implicitly converts to
T*
when needed to pass to C functions. - Support all of
std::vector
's methods, as long as they are compatible with the two demands earlier.
This is what I have:
/*
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
data()
all the time. Normally I'm fond of strict-ness, but IMO if you go as far as replacing vector with a C-style array wrapper then make it behave as a C-style array. And the size as template parameter is against my design principles for this array because I want a static-sized array, not necessarily compile-time fixed size. \$\endgroup\$T*
at the moment. Am I missing something? \$\endgroup\$operator pointer()
where pointer istypedef T* pointer
. Try passing the Carray to a C function like strcpy (make sure you pass aCarray<char>
ofc). \$\endgroup\$