I was trying to define my own class template Array<T> to practice the usage of templates.
The code I produced builds properly, but when executed it gives the following error
terminate called after throwing an instance of 'std::bad_array_new_length'
what(): std::bad_array_new_length
I think I have found a solution to the problem, but I would be interested to see if there was an underlying error in the previous code and if so, which one.
This is the code I previously wrote:
#include <iostream>
class Empty{
private:
char error;
public:
Empty(char e) : error(e) { std::cout << "Azione non disponibile, lista vuota" << std::endl;}
};
template <class T>
class Array;
template <class T>
std::ostream& operator<<(std::ostream&,const Array<T>&);
template <class T>
class Array{
friend std::ostream& operator<< <T> (std::ostream&,const Array<T>&);
private:
T* arr;
unsigned int size;
unsigned int capacity;
static T* copia(T* a, unsigned int s, unsigned int c){
if(c > 0) {
T* app = new T[c];
for (int i = 0; i<s; ++i) {
app[i] = a[i];
}
return app;
}else return nullptr;
}
public:
Array(int k = 0, const T& t = T()) : size(k > 0 ? k : 0), capacity(size), arr(k > 0 ? new T[size] : nullptr){
for (int i = 0; i < k ; ++i) arr[i] = t;
}
Array(const Array& a) : size(a.size), capacity(a.capacity), arr(copia(a.arr,a.size,a.capacity)){}
Array& operator=(const Array& a){
if(this != &a){
delete[] arr;
capacity = size = a.size;
arr = copia(a.arr,a.size,a.capacity);
}
return *this;
}
~Array(){delete[] arr;}
void pushBack(const T& t) {
if(size == capacity){
capacity > 0 ? capacity *= 2 : capacity = 1;
T* app = copia(arr,size, capacity);
delete[] arr;
arr = app;
}
++size;
arr[size-1] = t;
}
T popBack() {
if (size != 0) {
T temp = arr[size - 1];
--size;
return temp;
} else throw Empty('e');
}
};
template <class T>
std::ostream& operator<<(std::ostream& os ,const Array<T>& a){
for (int i = 0; i < a.size; ++i) {
os << a.arr[i] << ' ';
}
std::cout << std::endl;
return os;
}
int main(){
Array<int> a(5,5),e;
std::cout << a << std::endl;
a.pushBack(16);
a.pushBack(17);
a.pushBack(18);
std::cout << a << std::endl;
return 0;
}
If I run this code without the a.pushBack(x) function call, it works.
As soon as I insert even one function call, I get that error in the output.
While debugging, I realized that the line where I had written T* arr was not the correct one.
Knowing that the constructor follows the order of initialization of its own sub-objects, the first element to be constructed is the pointer.
Since I'm trying to create a vector of elements of T with dimension size, rightly gives me the error, as I have not yet initialized the integer size.
So I solved it by swapping the lines.
template <class T>
class Array{
friend std::ostream& operator<< <T> (std::ostream&,const Array<T>&);
private:
unsigned int size;
unsigned int capacity;
T* arr;
...
};
But at this point I wonder: why, if I don't make the function call, I don't get the same error, knowing that size at the time of construction is undefined?
Logically, the problem should also occur in that case, but everything seems to work:
PS: Don't count on the fact that I didn't handle the exception being thrown, the code is not yet fully complete, but for the moment I was keen to at least implement the Rule of Three.
Compiling with GCC 9.1.0 in jdoodle.com, I consistently got a bad_alloc runtime exception with your original code.
I added a new constructor with a different signature so I could see what value of size it was using to allocate the array
Note: Even the existence of this new ctor prevented the bad_alloc error, whether it was called or not.
Array(char c, int k = 0, const T& t = T()) :
size(k > 0 ? k : 0),
capacity(size),
arr(DebugInit(size)){
for (int i = 0; i < k ; ++i) arr[i] = t;
}
T* DebugInit( unsigned long size_init )
{
std::cout << "DebugInit size_init=" << size_init << " cap=" << capacity << std::endl;
return size_init > 0 ? new T[size_init] : nullptr;
}
The results seemed to be random when using this new ctor.
size_init varied each time, which is consistent with using a member field which has not yet been initialised.
In the original code, it may be that size happened to consistently have 0 in it.
By adding more code, even if it was never called, the compiled version would then access a random but now non-zero value for size.
It seems like classic "undefined behaviour". If you use size before it is initialised, there are no guarantees about what will be in it.
If you're lucky, size will consistently return 0 before it is initialised and you'll get an allocation error.
But a small change to the code may start returning random values for size.
If the uninitialised size has a much larger value than the one you intended, you won't see a problem until later, or maybe never.
After a bit more playing around with the code, the std::bad_alloc exception came back! So, definitely no guarantees!
Related
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.)
I am running into this error...
...with this minimal executable code to reproduce the error and ...
#include <iostream>
class arrayClass
{
private:
int _elements;
int _array[];
public:
arrayClass()
: _elements(32)
{
//If i is smaller than 2 the "Stack Smashing Detected" error shows up, why is that?
//If i is 2 or more, no error appears
//(f.e. int i = 0 or int i = 1 doesn't work, int i = 2 or higher works - why is that?)
for(int i = 0; i < _elements; ++i){
_array[i] = 0;
}
}
int get(int index){
return _array[index];
}
};
int main()
{
arrayClass arr;
std::cout << arr.get(2) << std::endl;
return 0;
}
...it doesn't matter if I initiate _elements with the initialization list or at with the
attribute itself with f.e.32 or whatever number.
If I pass the int value to construct the arrayClass(int) in addition to the arrayClass() constructor, the error doesn't shows up.
If I construct the arrayClas(int) with a value alone, it can also just be used with the 2nd slot upwards.
So my question is:
Why couldn't I initiate the 1st and 2nd array slot of a default array[]?
Or the other way around, why is it possible to assign an empty array[] to a class without a value and not f.e. _array[32] without an error but the one with assigning array[0] = 0; and array[1] = 0; ?
(And yes, I am aware of vectors, I need to use arrays for various reasons)
Because you never allocate memory for the array to begin with, everything is undefined behavior. I don't even know what int _array[] evaluates to without a size specifier. I'll look that up later.
Change your construct code to "new" the array. And have a destructor to delete it.
class arrayClass
{
private:
int _elements;
int* _array;
public:
arrayClass()
: _elements(32)
{
_array = new int[_elements];
memset(_array, '\0', sizeof(array[0])*_elements);
}
int get(int index){
return _array[index];
}
~arrayClass()
{
delete [] _array;
}
};
Or if you can have a fixed number of elements, explictly size the array when it's declared:
class arrayClass
{
private:
int _array[32];
public:
arrayClass()
: _array() // this will zero-init the array
{
}
int get(int index){
return _array[index];
}
};
int _array[]; is a flexible array and isn't allowed in standard C++. It does not allocate memory so when you access any element in the array, you have undefined behavior.
I am aware of vectors, I need to use arrays for various reasons
In reality there are very few valid reasons so I expect the various reasons you mention to be artificial. If you need to pass the data to a function, like void func(int*, size_t elements);, you can still use a std::vector<int>. Just pass its data() and size() as arguments.
In C++ you should typically use a std::vector<int> for cases like this.
Example:
#include <iostream>
#include <vector>
class arrayClass
{
private:
std::vector<int> _array;
public:
arrayClass(size_t s = 32)
: _array(s)
{}
size_t size() const {
return _array.size();
}
int get(size_t index) const {
return _array[index];
}
};
int main()
{
arrayClass arr;
std::cout << arr.get(10) << std::endl;
return 0;
}
An alternative, if your arrayClass has a fixed number of elements:
#include <array>
class arrayClass
{
private:
std::array<int, 32> _array;
public:
arrayClass()
: _array{}
{}
size_t size() const {
return _array.size();
}
int get(size_t index){
return _array[index];
}
};
If the extra space a std::vector consumes (usually 4 or 8 bytes) is a real concern, you could make a similar class that only stores the pointer to the allocated memory and the size. It could look like this (but doesn't have the ability to grow/shrink like a vector has):
#include <iostream>
#include <algorithm>
#include <memory>
#include <type_traits>
template<typename T, std::enable_if_t<std::is_default_constructible_v<T>>* = nullptr>
class arrayClass {
public:
using value_type = T;
arrayClass(size_t size = 32) :
_size(size),
_array(std::make_unique<T[]>(_size))
{}
// copy constructor
arrayClass(const arrayClass& rhs) :
_size(rhs._size),
_array(std::make_unique<T[]>(_size))
{
static_assert(std::is_copy_assignable_v<T>, "T must be copy assignable");
std::copy(rhs._array.get(), rhs._array.get() + _size, _array.get());
}
arrayClass(arrayClass&&) = default; // move constructor
// copy assignment operator
arrayClass& operator=(const arrayClass& rhs) {
*this = arrayClass(rhs); // copy construct and move assign
return *this;
}
arrayClass& operator=(arrayClass&&) = default; // move assignment operator
// accessing element at index
T& operator[](size_t index) { return _array[index]; }
const T& operator[](size_t index) const { return _array[index]; }
// bounds checking access to element
T& at(size_t idx) {
if(idx >= _size)
throw std::out_of_range(std::to_string(idx) + ">=" + std::to_string(_size));
return _array[idx];
}
const T& at(size_t idx) const {
if(idx >= _size)
throw std::out_of_range(std::to_string(idx) + ">=" + std::to_string(_size));
return _array[idx];
}
size_t size() const { return _size; }
// support for iterating over the elements in the array
const T* cbegin() const { return _array.get(); }
const T* cend() const { return _array.get() + _size; }
const T* begin() const { return cbegin(); }
const T* end() const { return cend(); }
T* begin() { return _array.get(); }
T* end() { return _array.get() + _size; }
private:
size_t _size;
std::unique_ptr<T[]> _array;
};
using intArray = arrayClass<int>;
int main() {
try {
intArray arr1(10);
// the added iterator support makes range-based for-loops possible:
for(int& v : arr1) {
static int i=0;
v = ++i;
}
intArray arr2;
arr2 = arr1; // copy assign
for(size_t i=0; i < arr2.size(); ++i) {
std::cout << arr2[i] << '\n'; // access using operator[] works too
}
std::cout << arr2.at(10) << '\n'; // throws out_of_range exception
}
catch(const std::out_of_range& ex) {
std::cerr << ex.what() << '\n';
}
}
I had refrenced a few other similar questions, they would often point to circularity being the problem. But I cannot see that anywhere within my code.
arrayi.cpp:
#include "arrayi.h"
// Member function definitions for class Array
// Initialize static data member at file scope
template<typename T>
int Array<T>::arrayCount = 0; // no objects yet
// Default constructor for class Array
template<typename T>
Array<T>::Array(int arraySize)
{
++arrayCount; // count one more object
size = arraySize; // default size is 10
ptr = new int[size]; // create space for array
assert(ptr != 0); // terminate if memory not allocated
int i;
for (i = 0; i < size; i++)
ptr[i] = 0; // initialize array
}
arrayi.h:
#ifndef ARRAYI_H_
#define ARRAYI_H_
#include <iostream>
#include <cstdlib>
#include <cassert>
using namespace::std;
template<typename T> class Array;
template<typename T>
ostream &operator<< (ostream& output, const Array<T> &a);
template<typename T>
class Array
{
friend ostream &operator<< <>(ostream &output, const Array<T> &a);
public:
Array(int = 10); //constructor
Array(const Array &); //copy constructor
private:
int *ptr; //ptr to first array element
int size; //size of the array
static int arrayCount; // #of arrays instantiated
};
#include "arrayi.t"
#endif
arrayi.t:
#ifndef ARRAYI_T_
#define ARRAYI_T_
#include <iostream>
#include <cstdlib>
#include <cassert>
using namespace::std;
// Default constructor for class Array
template<typename T>
Array<T>::Array(int arraySize)
{
cout << "calling the constructor \n";
}
// Overloaded output operator for class Array
template<typename T>
ostream &operator<<(ostream &output, const Array<T> &a)
{
int i;
output << "{ ";
for (i = 0; i < a.size; i++)
{
output << a.ptr[i] << ' ';
if ((i + 1) % 10 == 0)
output << "}" << endl;
} //end for
if (i % 10 != 0)
output << "}" << endl;
return output; // enables cout << x << y;
}
#endif
I have been scanning my code up and down for quite some hours now, so any help would be much appreciated, thanks in advance! Any messy or broken looking code may be because this is from a work in progress, but there are no errors but the mentioned one at the moment. Everything shown compiles when the function "Array::Array(int arraySize)" is removed.
arrayi.t defines
Array<T>::Array(int arraySize)
{
cout << "calling the constructor \n";
}
arrayi.cpp defines
template<typename T>
Array<T>::Array(int arraySize)
{
++arrayCount; // count one more object
size = arraySize; // default size is 10
ptr = new int[size]; // create space for array
assert(ptr != 0); // terminate if memory not allocated
int i;
for (i = 0; i < size; i++)
ptr[i] = 0; // initialize array
}
Two definitions of the same function with the same parameters is not allowed.
Solution:
Pick the one that is the real constructor. Delete the other one. If you select the implementation in arrayi.cpp, ensure that it is visible to translation units that require it. This most likely means move it to arrayi.t.
Give Template static variable a read for heads-up on another problem coming your way.
I am having trouble building a class that makes sure the user does not access an element that is off the end of the array, by building a class that mimics the behavior of an array, but adds a check. That is, this class will create a sequence of elements of a given type, and allow access to these elements with the [] bracket operator, but it will check to make sure that the user does not try to do something with an element that doesn't exist.
Here is the instructions on building it.
I have no idea how to make an index operator for this case. Please help me. Thanks!
Here is my 3 files I have so far...
dvd.h
class dvdArray{
dvd *elt;
int size;
static int defaultSieze;
int getSize();
void display();
dvdArray(unsigned int sz);
dvdArray();
dvdArray(dvdArray &obj);
~dvdArray();
};
dvd.cpp
dvd::dvd(){
id =0;
int n=5;
title = new char [n];
director = new char [n];
title[0] = '\0';
director[0] = '\0';
}
dvd::~dvd(void)
{
}
dvdArray::dvdArray(unsigned int sz){
elt = new dvd[sz];
}
dvdArray::dvdArray(){
size = defaultSieze;
elt = new dvd[defaultSieze];
}
dvdArray::dvdArray(dvdArray &obj){
size = obj.size;
elt = new dvd[defaultSieze];
for (int i=0; i!='\0'; ++i) {
elt[i]=obj.elt[i];
}
}
dvdArray::~dvdArray(void)
{
}
The easiest / cleanest thing to do is to derive from std::vector (or std::array if it suits your purposes better), which is safe as long as you don't then delete the object using a std::vector*, and as long as the functions you want to do checked access accept the parameter as a checked_vector and not a std::vector*/&...
Example:
template <typename T>
class checked_vector : public std::vector<T>
{
public:
// ...forwarding constructors
using std::vector::vector;
T& operator[](size_t n) { return at(n); }
const T& operator[](size_t n) const { return at(n); }
};
Note: that won't protect you from invalid use of iterators, such as incrementing them too far or adding an illegal offset to them.
If - for whatever reason - you're determined to use your own implementation...
dvd& dvdArray::operator[](size_t n)
{
if (n >= size)
throw std::runtime_error("invalid array index");
return dvd[sz];
}
const dvd& dvdArray::operator[](size_t n) const
{
if (n >= size)
throw std::runtime_error("invalid array index");
return dvd[sz];
}
I would do something simple.. General purpose array, bounds checked...
Not sure why you would need that however... Vectors are a very good alternative to arrays.
template <typename T> class myArray{
size_t size;
T *arr;
void allocate(size_t s){
if(s<1) arr = NULL;
else arr = new T[s];
size = s;
}
public:
myArray(){ allocate(10); } //default constructor
myArray(size_t s){ allocate(s); }
~myArray(){ if(arr!=NULL) delete[] arr; arr=NULL; }
T& operator[] (size_t s) {
if(s>=size) throw Error(); //or do whatever
else return arr[s];
}
};
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.