I'm writing my own dynamic array class in C++ (similarly to std::vector), and I'm running into a problem when having a dynamic array containing dynamic arrays.
Basically when having an array of all data types (int, double, float, std::string etc.) there is no problem and all the functionalities of the class works great.
When the data type is another array though something messes up and there is an error raising in the end of the program (free(): double free detected in tcache 2)
All of the code:
DynamicArray.h:
#pragma once
#include <iostream>
namespace Utils
{
template <typename T>
class DynamicArray
{
private:
size_t array_length;
T* array;
public:
~DynamicArray();
DynamicArray();
DynamicArray(const int& initialLength);
void Print();
size_t GetLength() const;
void AddItem(const T& newItem);
// TODO: void AddItems(const T* newItemsArray);
void RemoveItem(int index);
T& GetItem(int index);
void SetItem(const int& index, const T& newValue);
T& operator [](int index) const;
void ResetArray(T resetValue);
};
}
#include "DynamicArray.cpp"
DynamicArray.cpp:
#include "DynamicArray.h"
template<typename T>
Utils::DynamicArray<T>::~DynamicArray()
{
std::cout << "before del" << this->array_length << "\n";
if (this->array_length > 0)
delete[] this->array;
std::cout << "after del\n";
}
template<typename T>
Utils::DynamicArray<T>::DynamicArray()
{
this->array_length = 0;
}
template<typename T>
Utils::DynamicArray<T>::DynamicArray(const int& initialLength)
{
this->array_length = initialLength;
T* new_array = new T[initialLength];
this->array = new_array;
}
template<typename T>
void Utils::DynamicArray<T>::Print()
{
for (size_t i = 0; i < this->array_length; i++)
std::cout << this->array[i] << std::endl;
}
template<typename T>
size_t Utils::DynamicArray<T>::GetLength() const
{
return this->array_length;
}
template<typename T>
void Utils::DynamicArray<T>::AddItem(const T& newItem)
{
T* new_array = new T[this->array_length + 1];
for (size_t i = 0; i < this->array_length; i++)
new_array[i] = this->array[i];
new_array[array_length] = newItem;
// Releasing the memory of array
if (this->array_length != 0)
{
delete[] this->array;
this->array = nullptr;
}
this->array_length += 1;
this->array = new_array;
}
template<typename T>
void Utils::DynamicArray<T>::RemoveItem(int index)
{
T* new_array = new T[this->array_length - 1];
int temp_index = 0;
for (size_t i = 0; i < this->array_length; i++)
{
if (i != index)
{
new_array[temp_index] = this->array[i];
temp_index++;
}
}
// Releasing the memory of array
delete[] this->array;
this->array = nullptr;
this->array_length -= 1;
this->array = new_array;
}
template <typename T>
T& Utils::DynamicArray<T>::GetItem(int index)
{
return this->array[index];
}
template<typename T>
T& Utils::DynamicArray<T>::operator[](int index) const
{
return this->array[index];
}
template <typename T>
void Utils::DynamicArray<T>::ResetArray(T resetValue)
{
for (int i = 0; i < this->array_length; i++)
this->array[i] = resetValue;
}
template <typename T>
void Utils::DynamicArray<T>::SetItem(const int& index,const T& newValue)
{
this->array[index] = newValue;
}
main function:
#include <iostream>
#include "DynamicArray.h"
int main()
{
Utils::DynamicArray<Utils::DynamicArray<double>> outputs;
Utils::DynamicArray<double> singleOutput;
singleOutput.AddItem(1);
singleOutput.AddItem(1);
outputs.AddItem(singleOutput);
}
Output given when running the program:
before del2
after del
before del1
before del2
free(): double free detected in tcache 2
Aborted (core dumped)
Any ideas? No matter what I tried nothing worked..
You failed to write proper copy constructor and assignment operators:
DynamicArray(DynamicArray const& rhs); // copy constructor
DynamicArray& operator=(DynamicArray const& rhs); // copy assignment
When you don't write these yourself, they are generated with shallow copy semantics. Since your class "owns" a pointer, if you shallow copy it, then two instances o DynamicArray both own the same pointner, and when one is destroyed, it destroys the data pointed to by the other. And when the other is destroyed, you get a double free.
To write these you need to allocate memory and do a full copy.
(You also would eventually want to write a move constructor, and move assignment operator.)
The element declared on the stack in main() is also copied into the other DynamicArray. The double free occurs when the stack of main is cleaned up: first delete is in the destructor of singleOutput, and the second delete is in the destructor of outputs, which holds an element that has the same pointer as singleOutput.
You also leave your "array" member uninitialized in the default constructor. That does not set it to zero, it leaves garbage in it. (Which could be zero, but might not be.)
Related
I create a class Vector that contain two variables that are template variable, I am trying to build such a dictionary that tell mark of a specific student.
The problem is I am struggling dynamic allocating memory with template. I have to do this without map or STL help. Can you explain, how to allocate them properly.
#define DEF_CAPACITY 20
template <class U, class T>
class Vector {
protected:
T* _data;
U* _keys;
int _size; //size in use
int _capacity; //available capacity
public:
//constructors
Vector(int capacity = DEF_CAPACITY);
~Vector();
int getSize() const { return size; }
void insert(U key, T data);
T operator[](U key);
};
template<class U, class T>
inline Vector<U, T>::Vector(int capacity)
{
this->_capacity = capacity;
this->_size = 0;
}
template<class U, class T>
Vector<U, T>::~Vector()
{
if (_data)
delete[] _data;
if (_keys)
delete[] _keys;
}
template<class U, class T>
void Vector<U, T>::insert(U key, T data)
{
_keys[_size] = new U;
//Maybe I have to do something like that, but all options I test doesn't work
//keys[_size] = new U[Some size];
_keys[_size] = key;
_data[_size] = new T;
_data[_size] = data;
_size++;
}
template<class U, class T>
T Vector<U, T>::operator[](U key)
{
int index = 0;
for (int i = 0; i < size; i++)
if (_keys[i] == key)
index = i;
return _data[index];
}
int main() {
Vector<string, int> grades;
grades.insert("john", 90);
grades.insert("marc", 100);
grades.insert("ron", 87);
grades.insert("lynda", 95);
//...
grades.insert("Tome", 93);
//...
cout << grades["marc"] << endl;
cout << grades["lynda"] << endl;
cout << grades["john"] << endl;
return 0;
}
To allocate n elements of type T you use:
T* array = new T[n];
so to allocate space for your keys and values:
// be aware: this will call the constructor for each element!!!
_keys = new U[n];
_data = new T[n];
If your "Vector" has a fixed capacity, this should be done in your constructor:
template<class U, class T>
inline Vector<U, T>::Vector(int capacity)
: _data(new T[capacity]);
, _keys(new U[capacity]);
, _size(0);
, _capacity(capacity);
{
}
after that, you can insert key/value pairs like this:
template<class U, class T>
void Vector<U, T>::insert(const U& key, const T& data)
{
if(_size == _capacity)
throw std::out_of_range("no space left!!!");
_keys[_size] = key; // copy assignment
_data[_size] = data; // copy assignment
_size++;
}
This design will unnecessarily call constructors for unused keys and values.
You could also do this with malloc and free to prevent this, though I wouldn't recommend going that way and instead use the standard library.
I am trying to code a template array class and overloading some operators. Part of my code is as follows:
template.h:
main.cpp:
C2679 binary '<': no operator found which takes a right-hand operand of type 'Array<int>' (or there is no acceptable conversion)
What is causing this error?
What is causing this error?
You are using
return this < a;
this is a pointer while a is a reference to an object. It's analgous to comparing an int* with an int.
int a = 10;
int b = 11;
int* p = &b;
if ( p < a ) { ... }
That is not right.
That function needs to be implemented differently. You need to compare each item of the arrays and return an appropriate value.
template<typename T>
bool Array<T>::operator<(const T& a)
{
int lowerLength = std::min(this->arrLength, a.arrLengh);
for ( int i = 0; i < lowerLength; ++i )
{
if ( this->myArray[i] != a.myArray[i] )
{
return (this->myArray[i] < a.myArray[i]);
}
}
// If we get here, return a value based on which array has more elements.
return (this->arrLength < a.arrLengh)
}
While at it, make the member function a const member function.
bool Array<T>::operator<(const T& a) const;
and change the implementation accordingly.
In findBigPos() (and your other functions in Driver.cpp, too), you should be passing arr by reference, not by pointer. When arr is a pointer, arr[index] is the same as *(arr + index) - it performs pointer arithmetic to dereference the pointer at a given offset, it does not index into your array at all. That is why the compiler thinks you are comparing Array<int> objects, and not calling your operator[].
Try this instead:
#include "wallet.h"
#include "currency.h"
#include "array.h"
#include <iostream>
#include <string>
using namespace std;
template<typename T>
void recurSelectionSort(Array<T>&, int size, int index);
template<typename T>
int findBigPos(Array<T>&, int size, int index);
int main() {
//code
}
template<typename T>
void recurSelectionSort(Array<T>& arr, int size, int index) // move the biggest element in arr to index
{
if (index == size) {
return;
}
else if (index < size) {
int bigPos = findBigPos(arr, size, index); //position of "biggest" element
T bigVal = arr[bigPos]; //the value of "biggest" element
T copy = arr[index]; //copy of wat ever is going to get copy
arr[index] = bigVal;
arr[bigPos] = copy;
recurSelectionSort(arr, size, index + 1);
cout << arr;
}
}
template<typename T>
int findBigPos(Array<T>& arr, int size, int index)
{
if (index == size - 1) {
return index;
}
else
{
int bigPos = findBigPos(arr, size, index + 1);
return arr[bigPos] < arr[index] ? index : bigPos;
}
}
That said, there are some issues with your Array class itself, too.
You are not implementing the Rule of 3/5/0. Your class is lacking a copy constructor and a copy assignment operator, and in C++11 and later a move constructor and a move assignment operator.
you don't have a const version of your operator[] for your operator<< to use, since it takes a reference to a const Array<T> as input.
your operator[] is not checking for index < 0. And it would be better to throw a std::out_of_range exception instead of an int. If it throws at all. Typically, an array's operator[] should not perform bounds checking at all. That is why containers like std::vector and std::string have a separate at() method for handling bounds checking.
your operator< is not implemented correctly at all. You can't compare a Array<T>* pointer to a const T& reference. You probably meant to dereference the this pointer before comparing it to a, but then that would lead to an endless recursive loop. What you should do instead is change const T& a to const Array<T> &a and then compare the contents of this to the contents of a.
Try this:
#ifndef ARRAY_HEADER
#define ARRAY_HEADER
#include <iostream>
#include <stdexcept>
#include <utility>
template<typename T>
class Array
{
private:
int arrLength;
T* myArray;
public:
Array(int length = 5);
Array(const Array &a);
Array(Array &&a);
virtual ~Array();
int getLength() const;
Array& operator=(Array a);
T& operator[](int index);
const T& operator[](int index) const;
bool operator<(const Array &a) const;
friend std::ostream& operator<<(std::ostream &output, const Array &arr)
{
int arrSize = arr.getLength();
for (int i = 0; i < arrSize; i++) {
output << arr[i] << " ";
}
return output;
}
};
template<typename T>
Array<T>::Array(int length)
{
myArray = new T[length];
arrLength = length;
}
template<typename T>
Array<T>::Array(const Array<T> &a)
{
myArray = new T[a.arrLength];
arrLength = a.arrLength;
for(int i = 0; i < arrLength; ++i)
myArray[i] = a.myArray[i];
}
template<typename T>
Array<T>::Array(Array<T> &&a)
{
arrLength = a.arrLength;
myArray = a.myArray;
a.myArray = nullptr;
a.arrLength = 0;
}
template<typename T>
Array<T>::~Array()
{
delete[] myArray;
}
template<typename T>
int Array<T>::getLength() const
{
return arrLength;
}
template<typename T>
Array<T>& Array<T>::operator=(Array<T> a)
{
using std::swap;
swap(myArray, a.myArray);
swap(arrLength, a.arrLength);
return *this;
}
template<typename T>
T& Array<T>::operator[](int index) {
if ((index < 0) || (index >= arrLength)) {
throw std::out_of_range("index is out of range");
}
return myArray[index];
}
template<typename T>
const T& Array<T>::operator[](int index) const {
if ((index < 0) || (index >= arrLength)) {
throw std::out_of_range("index is out of range");
}
return myArray[index];
}
template<typename T>
bool Array<T>::operator<(const Array<T> &a) const
{
if (arrLength < a.arrLength)
return true;
if (arrLength == a.arrLength)
{
for (int i = 0; i < arrLength; ++i)
{
if (myArray[i] != a.myArray[i])
return myArray[i] < a.myArray[i];
}
}
return false;
}
#endif
Suppose we have a templated c++ list class. Yes, vector exists, but the point is to know how to handle this problem.
The Constructor allocates a block of n objects of type T but does not initialize because they are not used yet.
In the add method, we wish to copy in a new object, but using operator = is not possible because operator = would first destroy the existing object, which was never initialized. How does one copy in an object into data[used] ?
#include <string>
template<typename T>
class DynArray {
private:
int capacity;
int used;
T* data;
public:
DynArray(int initialCap) : capacity(initialCap), used(0), data((T*)new char[sizeof(T)*capacity]) {}
void add(const T& e) {
//TODO: if the dynarray is full, grow
data[used++] = e; //ERROR! Should use copy constructor!!!
}
};
int main() {
DynArray<std::string> a(5);
a.add(std::string("abc"));
}
You should use placement new:
void add(const T& e) {
//TODO: if the dynarray is full, grow
new (data + used) T(e);
used++;
}
Placement new constructs an object in already allocated memory.
For what you are attempting to do, you need to call T's copy constructor using placement-new. And don't forget to implement the Rule of 3/5/0 as well:
template<typename T>
class DynArray {
private:
int capacity;
int used;
T* data;
public:
DynArray(int initialCap = 0) : capacity(0), used(0), data(0) {
reserve(initialCap);
}
DynArray(const DynArray &src) : capacity(0), used(0), data(0) {
reserve(src.capacity);
for(int i = 0; i < src.used; ++i) {
add(src.data[i]);
}
}
// C++11 and higher only...
DynArray(DynArray &&src) : capacity(src.capacity), used(src.used), data(src.data) {
src.capacity = src.used = 0;
src.data = 0;
}
~DynArray() {
clear();
delete[] reinterpret_cast<char*>(data);
}
DynArray& operator=(const DynArray &rhs) {
if (&rhs != this) {
DynArray(rhs).swap(*this);
}
return *this;
}
// C++11 and higher only...
DynArray& operator=(DynArray &&rhs) {
DynArray(std::move(rhs)).swap(*this);
return *this;
}
void swap(DynArray &other) {
std::swap(data, other.data);
std::swap(used, other.used);
std::swap(capacity, other.capacity);
}
void clear() {
resize(0);
}
void reserve(int newCap) {
// TODO: round up newCap to an even block size...
if (newCap <= capacity) return;
T *newData = reinterpret_cast<T*>(new char[sizeof(T) * newCap]);
for(int i = 0; i < used; ++i) {
new (newData + i) T(data[i]);
}
delete[] reinterpret_cast<char*>(data);
data = newData;
capacity = newCap;
}
void resize(int newSize) {
if (newSize < 0) newSize = 0;
if (newSize == used) return;
if (newSize > used) {
reserve(newSize);
for(int i = used; i < newSize; ++i) {
new (data + i) T();
++used;
}
}
else {
for(int i = used-1; i >= newSize; --i) {
data[i]->~T();
--used;
}
}
}
void add(const T& e) {
if (used == capacity) {
reserve(capacity * 1.5);
}
new (data + used) T(e);
++used;
}
};
#include <string>
int main() {
DynArray<std::string> a(5);
a.add("abc");
}
The DynArray class has a type of T, so you should simply allocate an array of type T with size of initialCap, which is simply
new T[initialCap];
For built-in types, e.g. int, the elements are left uninitialized.
For others, like string, the default constructor of T is called to initialize the elements.
In the add method, we wish to copy in a new object, but using operator = is not possible because operator = would first destroy the existing object
data[used++] = e; This is perfectly fine. It assigns e to data[used] - calls the assignment operator of the string, and it won't cause any troubles. However, when your array grows, you would need to allocate new arrays with double capacity, copy over the elements, and destroy the old data.
I am trying (struggling) writing a generic vector class using std::unique_ptr. In my constructor I get this exception thrown:
Exception thrown: write access violation.
std::unique_ptr<int [0],std::default_delete<int [0]> >::operator[](...) returned nullptr.
This is the associated function:
template <class T>
Vector<T>::Vector(int n, const T &value) {
capacity = (n > initial_capacity) ? n : initial_capacity;
size = n;
for (int i = 0; i < n; i++) {
data[i] = value;
}
}
I also get an error here in the main.cpp file:
assert(nullVector.getCapacity() == 100);
I believe this is because I did not set the capacity in the std::unique_ptr if that is even possible.
Here is part of my header file:
#ifndef Vector_h
#define Vector_h
template <class T>
class Vector {
private:
static constexpr int initial_capacity = 100;
// Instance variables
int capacity = 0;
int size = 0;
std::unique_ptr<T[]> data = nullptr;
void deepCopy(const Vector<T> &source) {
capacity = source.size + initial_capacity;
for (int i = 0; i < source.size; i++) {
data[i] = source.data[i];
}
size = source.size;
}
void expandCapacity() {
auto oldData = std::move(data);
capacity *= 2;
for (int i = 0; i < size; i++) {
data[i] = oldData[i];
}
}
public:
// Constructors
Vector() = default; // empty constructor
Vector(int n, const T &value); // constructor
Vector(Vector<T> const &vec); // copy constructor
Vector<T>& operator=(Vector<T> const &rhs); // assignment operator
// Rule of 5
Vector(Vector<T> &&move) noexcept; // move constructor
Vector& operator=(Vector<T> &&move) noexcept; // move assignment operator
~Vector(); // destructor
// Overload operators
T& operator[](int index);
T const& operator[](int index) const;
bool operator==(const Vector<T>&) const;
//Vector<T>& operator+=(const Vector<T> &other) {
// Vector<T> newValue(size + other.size);
// std::copy(this->data, this->data + this->size, newValue.data);
// std::copy(other.data, other.data + other.size, newValue.data + this->size);
// newValue.swap(*this);
//}
friend Vector<T>& operator+(Vector<T> &source1, Vector<T> &source2) {
int n = source1.getSize() + source2.getSize();
Vector<T> newSource(n,0);
for (int i = 0; i < source1.size; i++) {
newSource[i] = source1[i];
}
for (int i = 0; i < source2.size; i++) {
newSource[i + source1.getSize()] = source2[i];
}
return newSource;
}
friend std::ostream& operator<<(std::ostream &str, Vector<T> &data) {
data.display(str);
return str;
}
// Member functions
void swap(Vector<T> &other) noexcept;
void display(std::ostream &str) const;
int getSize() const { return size; }
int getCapacity() const { return capacity; }
bool empty() const { return size == 0; }
void clear() { size = 0; }
T get(int index) const;
void set(int index, const T &value);
void set(int index, T &&value);
void insert(int index, const T &value);
void insert(int index, T &&value);
void remove(int index);
void push_back(const T &value);
void pop_back();
};
template <class T>
Vector<T>::Vector(int n, const T &value) {
capacity = (n > initial_capacity) ? n : initial_capacity;
size = n;
for (int i = 0; i < n; i++) {
data[i] = value;
}
}
Here is part of the main.cpp file:
#include <algorithm>
#include <initializer_list>
#include <iostream>
#include <cassert>
#include <ostream>
#include "Vector.h"
int main() {
///////////////////////////////////////////////////////////////////////
///////////////////////////// VECTOR //////////////////////////////////
///////////////////////////////////////////////////////////////////////
Vector<int> nullVector; // Declare an empty Vector
assert(nullVector.getSize() == 0); // Make sure its size is 0
assert(nullVector.empty()); // Make sure the vector is empty
assert(nullVector.getCapacity() == 100); // Make sure its capacity is greater than 0
}
There is no such thing as a "capacity" of a unique_ptr. All an std::unique_ptr does is it holds on to a dynamically allocated object. It does not allocate an object by itself. Use std::make_unique() or new to create an new object and assign to your unique_ptr to hold on to.
I don't see you allocating any memory anywhere in your code. Unless you do allocate memory for your vector somewhere in a piece of code you didn't show, your data will just point to nullptr and trying to dereference it will crash (or worse). At least your expandCapacity() method does not seem to allocate any memory…
You probably should have a look at some material to learn about unique_ptr and smart pointers in general. For example: How to declare std::unique_ptr and what is the use of it? or this.
I am reading Programming Priciples and Practice Using C++ chapter 17-19, and trying to write my version of Vector. This is my code:
#include <stdexcept>
#include <exception>
using namespace std;
struct Range_error:out_of_range{
int index;
Range_error(int i):out_of_range("Range error"),index(i){}
};
template<class T, class A = allocator<T>> struct Vector_base{
A alloc;
T* elem;
int sz; // number of elements
int space; // number of elements plus "free space"/"slots" for new elements("the current allocation")
void copy(const Vector_base& arg)
{
T* p = alloc.allocate(arg.sz);
for(int i=0; i<arg.sz; ++i) alloc.construct(&p[i], arg.elem[i]);
elem = p;
sz = arg.sz;
space = arg.space;
};
Vector_base(): elem(alloc.allocate(0)), sz(0), space(0) {};
Vector_base(int n):elem(alloc.allocate(n)), sz(n), space(n) {};
Vector_base(const A& a, int n):alloc(a), elem(a.allocate(n)), sz(n), space(n){};
Vector_base(const Vector_base& arg) {copy(arg);}
~Vector_base() {alloc.deallocate(elem, space);}
};
template<class T, class A = allocator<T>> class Vector : private Vector_base<T,A>{
public:
Vector() : Vector_base(){};
Vector(int n) : Vector_base(n) {for(int i=0; i<this->sz; ++i) this->alloc.construct(&this->elem[i], T());}
Vector(const Vector& arg) : Vector_base(arg) {};
Vector& operator=(const Vector&);
~Vector() {};
int size() const {return this->sz;}
int capacity() const {return this->space;}
void resize(int newsize, T val=T());
void push_back(const T& val);
void pop_back(); // delete the last element
void reserve(int newalloc);
T& operator[](unsigned int n)
{
return this->elem[n];
}
const T& operator[](unsigned int n) const
{
return this->elem[n];
}
T& at(unsigned int n)
{
if(n<0 || this->sz<=n) throw Range_error(n);
return this->elem[n];
}
const T& at(unsigned int n) const
{
if(n<0 || this->sz<=n) throw Range_error(n);
return this->elem[n];
}
};
template<class T, class A> void Swap(Vector_base<T,A>& a, Vector_base<T,A>& b){
Vector_base<T,A> c(a);
a=b;
b=c;
}
template<class T, class A> Vector<T,A>& Vector<T,A>::operator=(const Vector<T,A>& a)
{
if(this == &a) return *this; // self-assignment, no work needed
if(a.sz<=sz){
for(int i=0; i<a.sz; ++i) elem[i] = a.elem[i];
sz=a.sz;
return *this;
}
T* p = new T[a.sz];
for(int i=0; i<a.sz; ++i) p[i] = a.elem[i];
delete elem;
elem=p;
space=sz = a.sz;
return *this;
}
template<class T, class A> void Vector<T,A>::reserve(int newalloc)
{
if(newalloc <= this->space) return;
Vector_base<T,A> b(this->alloc,newalloc);
for(int i=0; i<this->sz; ++i) this->alloc.construct(&b.elem[i], this->elem[i]); // copy
for(int i=0; i<this->sz; ++i) this->alloc.destroy(&this->elem[i]);
Swap<Vector_base<T,A>>(*this, b);
this->space = newalloc;
}
template<class T, class A> void Vector<T,A>::resize(int newsize, T val=T())
{
reserve(newsize);
for(int i=this->sz; i<newsize; ++i) this->alloc.construct(&this->elem[i], val);
for(int i=newsize; i<this->sz; ++i) this->alloc.destroy(&this->elem[i]);
this->sz = newsize;
}
template<class T, class A> void Vector<T,A>::push_back(const T& val)
{
if(this->space == 0) reserve(8);
else if(this->sz == this->space) reserve(2*(this->space));
this->alloc.construct(&this->elem[this->sz], val);
++(this->sz);
}
template<class T, class A> void Vector<T,A>::pop_back()
{
if(this->sz == 0) return;
this->alloc.destroy(&this->elem[--(this->sz)]);
if(this->sz <= (this->space)/2)
{
Vector_base<T,A> b(this->alloc,(this->space)/2);
for(int i=0; i<this->sz; ++i) this->alloc.construct(&b.elem[i], this->elem[i]); // copy
for(int i=0; i<this->sz; ++i) this->alloc.destroy(&this->elem[i]);
Swap<Vector_base<T,A>>(*this, b);
this->space /= 2;
}
}
when it compiled, the vc++ says "void Swap(Vector_base &,Vector_base &)' : could not deduce template argument for 'Vector_base,A> &' from 'Vector'". I know that *this is a Vector object but b is Vector_base object, but that's the book says. How can I make this code work? Is there any memory leak of this code? Thanks!
Your Swap function template is defined as template<class T, class A>.
So you should call it like: Swap<T,A>(*this, b); instead of Swap<Vector_base<T,A>>(*this, b);
And actually, calling Swap<T,A>(*this, b) is not correct at all. You should allocate memory of the requested size and copy existing elements to the new space. Then release memory that you initially allocated.
The second problem is in:
Vector_base(const A& a, int n)
: alloc(a), elem(a.allocate(n)), sz(n), space(n)
You cannot call a non-const member function on a const object.
So, use alloc.allocate(n) instead of a.allocate(n).
Update 1
Also, you're still mixing new and delete operators with alloc.allocate() and alloc.deallocate() in the assignment operator of Vector.
Update 2
Your assignment operator will be never called in Swap<T, A> because you're actually working with Vector_base, while assignment operator is defined for Vector. So Memberwise assignment will happen.
template<class T, class A> void Swap(Vector_base<T,A>& a, Vector_base<T,A>& b){
Vector_base<T,A> c(a);
a=b;
b=c;
}
That is b.elem and c.elem will point at the same address and alloc.deallocate will be called twice for it. Because first time ~Vector_base() will be called when c will go out of scope when Swap returns. And second time destructor will be called when b will go out of scope when reserve returns.
That's why you get an unhandled exception.
Is there any memory leak of this code?
Yes, there are memory leaks with your code. For instance:
void copy(const Vector_base& arg)
{
T* p = alloc.allocate(arg.sz);
for(int i=0; i<arg.sz; ++i)
alloc.construct(&p[i], arg.elem[i]); <--- what happens here
if construction fails ?
elem = p;
sz = arg.sz;
space = arg.space;
};
If the copy constructor throws, you leak the allocated memory and whatever resource the already constructed objects are holding.
A cure is:
for (int i = 0; i < arg.sz; ++i)
{
try { alloc.construct (&p[i], arg.elem[i]); }
catch (...)
{
// 1) Destroy the allocated objects
while (--i != 0) alloc.destroy (&p[i]);
// 2) Release p
alloc.deallocate (p, arg.sz);
// 3) rethrow exception
throw;
}
}
You have to do this consistently throughout the code, not only in the copy function. This is the number 1 reason why we use standard containers and not home-made ones.
For instance, exception safety is the reason why we have top and pop in std::stack: if there were only a pop method returning a copy of the object, what would happen if the copy construction throws an exception ?
If you want to implement your own containers (as an exercise or as a thoughtful decision), an excellent thing to do is to look at the implementation from your standard library. STL containers are templates, and all the code resides in the header files. Study it carefully, you'll learn many things.