Array Implementations --> DynamicArray class
template <typename T>
class DynamicArray {
public:
...
DynamicArray& operator=(const DynamicArray& other) {
if (this == &other)
return *this;
size_ = other.size_;
content_ = other.content_;
delete[] arr_;
arr_ = new T[size_];
for (size_type i=0; i<content_; ++i) {
arr_[i] = other.arr_[i]; // may throw
}
return *this;
}
...
private:
size_type size_;
size_type content_;
T* arr_;
};
This assignment operator is fine, unless T's assignment operator throws an exception in one of the assignments done
in the bolded line. In such cases, the DynamicArray object will be left in an invalid state because the
content_ member will suggest there are more valid members than there actually are.
template <typename T>
struct DynamicArrayImpl {
DynamicArrayImpl(const size_type size)
: size_(size), content_(0), arr_(size==0 ? 0 : new T[size]) {}
~DynamicArrayImpl()
{
delete[] arr_;
}
void swap(DynamicArrayImpl& other) throw()
// this function cannot throw an exception
{
std::swap(size_, other.size_);
std::swap(content_, other.content_);
std::swap(arr_, other.arr_);
}
void fill_from(const DynamicArrayImpl& other)
{
content_ = 0;
while (content_ < other.content_ && content_ < size_)
{
arr_[content_] = other.arr_[content_];
++content_;
}
}
size_type size_;
size_type content_;
T* arr_;
private:
// Do not allow copies
DynamicArrayImpl(const DynamicArrayImpl& other);
DynamicArrayImpl& operator=(const DynamicArrayImpl& other);
};
The constructor and destructor are straight-forward. The interesting things are the helper function swap() and
fill_from(). swap() simply swaps the members between this instance and the other DynamicArrayImpl instance. Like the
bolded comment says, swap() cannot throw an exception, and even explicitly says it using the throw() declaration. This is very
important for the exception safety of DynamicArray.
template <typename T>
class DynamicArray {
public:
typedef T value_type;
typedef T* iterator;
typedef const T* const_iterator;
typedef T& reference;
typedef const T& const_reference;
explicit DynamicArray(const size_type sz) : impl_(sz) {}
DynamicArray(const DynamicArray& other)
: impl_(other.impl_.size_) // may throw an exception
{
impl_.fill_from(other.impl_); // may throw an exception
}
DynamicArray& operator=(const DynamicArray& other)
{
DynamicArray arr_copy(other); // may throw an exception
impl_.swap(arr_copy.impl_); // cannot throw an exception
return *this;
}
iterator begin() { return impl_.arr_; }
iterator end() { return &impl_.arr_[content()]; }
const_iterator begin() const
{ return const_cast<DynamicArray&>(*this).begin(); }
const_iterator end() const
{ return const_cast<DynamicArray&>(*this).end(); }
size_type size() const { return impl_.size_; }
size_type content() const { return impl_.content_; }
bool empty() const { return content() == 0; }
// Unchecked references
reference operator[] (const size_type n)
{
if (n == size()-1) // last element accessed - increase size
resize(size()*2); // may throw an exception (see resize())
if (n >= content())
impl_.content_ = n+1;
return impl_.arr_[n];
}
const_reference operator[] (const size_type n) const
{ return const_cast<DynamicArray&>(*this)[n]; }
// Checked references
reference at(const size_type pos)
{
if (pos >= size())
throw OutOfRange();
return (*this)[pos];
}
const_reference at(const size_type pos) const
{ return const_cast<DynamicArray&>(*this).at(pos); }
private:
void resize(const size_type new_size)
{
DynamicArray resized_arr(new_size); // may throw an exception
resized_arr.impl_.fill_from(impl_); // may throw an exception
impl_.swap(resized_arr.impl_); // cannot throw an exception
}
private:
DynamicArrayImpl<T> impl_;
};
The interesting lines are marked in bold font-face. Let's review the function which contains these lines: