How to implement vector::clear()? (study purpose) - c++

I'm trying to implement my own vector class (for study purpose). Currently I am stuck at vector::clear(), how can I implement it? It can't be like this, right?
void clear() {
for (size_t i = 0; i < _size; ++i) {
_data = 0;//can't work with custom class
}
}
Or
void clear() {
delete[] _data;
_data = new T[_cap];
_size = 0;
}//can we make it better?
Or as simple as:
void clear() {
_size = 0;//fastest, but user can still access the data
}
T& operator[](size_t pos) {
if (pos >= _size) throw;//added to prevent from accessing "cleared" data
return _data[pos];
}
Did some digging through libcxx and found out they use alloc_traits::destroy, which destroy each element in _data???
Here is my class attribute
T* _data;
size_t _size;
size_t _cap;

If you keep a capacity larger than the size this means you need to account for allocated memory that doesn't hold any object. This means that the new T[_cap] approach simply doesn't work:
first and foremost your vector won't work for objects that are not default constructible
even for those that are, you will be creating more objects than requested and for some objects construction can be expensive.
the other problem is when you push_back when you sill have capacity you will be doing assignment instead of construction for the object (because an object already exists there)
So you need to decouple memory allocation from object creation:
allocate memory with operator new. Please note this is different than the new expression you are most familiar with.
construct an object in the allocated memory with in-place constructor (also kown as placement new)
destroy an object by explicitly calling the destructor
deallocate memory with operator delete
C++17 brings some utility functions for this purpose like: std::uninitialized_default_construct, uninitialized_copy, std::destroy etc.; find more in Dynamic memory management
If you want to be more generic like std::vector you can use allocators instead.
With this in mind, now answering your specific question about clear. The behavior of std::vector::clear is: "Erases all elements from the container. After this call, size() returns zero. [...] Leaves the capacity() of the vector unchanged". If you want the same behavior, this means:
void clear() noexcept
{
for (T* it = _data; it != _data + _size; ++it)
it->~T();
// or
std::destroy(_data, _data + size);
_size = 0;
}
As you can see implementing something like std::vector is far from trivial and requires some expert knowledge and techniques.
Another source of complications comes from strong exception safety. Let's consider just the case of push_back for exemplification. A naive implementation could do this (pseudocode):
void push_back(const T& obj)
{
if size == capacity
// grow capacity
new_data = allocate memory
move objects from _data to new_data
_data = new_data
update _cap
new (_data + _size) T{obj}; // in-place construct
++_size;
}
Now think what will it happen if the move constructor of one object throws while moving to the new larger memory. You have a memory leak and worst: you are left with some objects in your vector in a moved-from state. This will bring your vector in an invalid internal state. That's why is important that std::vector::push_back guarantees that either:
the operator is successful or
if an exception is thrown, the function has no effect.
In other words, it grantees that it never leaves the object in an "intermediary" or invalid state, like our naive implementation does.

The only responsibility of clear is to call the destructor on any objects that are in the array and set the array size to 0 (http://www.cplusplus.com/reference/vector/vector/clear/). Many implementations do not actually free the memory, but simply set the end and last pointers in the array to the beginning pointer after running through the vector and calling the destructor on each element. This is done as an optimization so that the memory is still available and the vector is ready to go if new elements are pushed onto the vector.
If you don't need that particular optimization, your version of clear() where you simply delete[] the data and then reallocate is perfectly reasonable. It all depends on what tradeoffs you want to make.

Related

How to free memory allocated by new[]?

I'm currently trying to create vector-like container. It uses memory allocated by new[] as it's base. the problem arises when I need to expand the array. I allocate a bigger chunk of memory with new[], then memcpy old memory into there and delete[] the old memory. Thing is, trying to store any pointer or any pointer-containing object inside result in memory corruption. So I need a way to free the memory used without destroying objects inside
Edit: Some code to understand the problem:
template<typename T>
class myvector
{
private:
T* _data;
size_t _size, _capacity;
static constexpr float multiplier = 1.5;
public:
void expand()
{
size_t e_size = sizeof(T);
size_t old_capacity = this->_capacity;
this->_capacity = (unsigned long)(float(this->_capacity) * myvector::multiplier);
T *tmp = new T[this->_capacity];
memcpy(tmp, this->_data, e_size * (old_capacity));
// this will destroy all the objects inside the container
// which will result in destruction of any allocated memory
delete[] this->_data;
// so now we have an array of invalid pointers. fun time
this->_data = tmp;
}
}
How to free memory allocated by new[]?
Using delete[]. This must be done before the pointer value is lost, and it must be done after the last use of the pointer value. And it must be done exactly once. And it must not be done for any other pointer than one that was returned by array new.
Thing is, trying to store any pointer or any pointer-containing object inside result in memory corruption.
Then there is a bug in how you use the objects. Storing such objects in your template should by itself not be a problem.
So I need a way to free the memory used without destroying objects inside
That is simply not possible. An object cannot exist without storage (except for special cases that don't apply here).
delete[] this->_data;
// so now we have an array of invalid pointers. fun time
Why would there be an array of invalid pointers? Do the pointers in the array point to this->_data?
Indeed, your data structure does not have stable addresses for its elements. Expansion will invalidate any references to the elements. If you need such stability, then you must use a node based data structure such as a linked list.
Your template does have a limitation that it is well defined only for trivially copyable classes. Perhaps you've overlooked this limitation. It is easy to get rid of this limitation: Simply use std::copy (or perhaps std::move from <algorithm>, depending on exception safety guarantees that you need) instead of std::memcpy.

I need to check if thrown exception from operator=?

Given the following code:
template <class T>
class A {
T* arr;
int size;
public:
A(int size) : arr(new T[size]) , size(size) {
}
//..
A& operator=(const A& a){
if(this == &a){
return *this;
}
this->size = a.size;
T* ar=new T[a.size];
for(int i=0 ; i<size ; i++){
ar[i]=a.arr[i]; // I need to do it at "try-catch" ?
}
delete[] this->arr;
this->arr=ar;
return *this;
}
//...
};
When I copy the elements from the given array, do I need to do it with a try-catch clause or not? is it a good idea or not?
I can see that potentially your T copy could throw due to its own alloc failure or other reasons.
On the other hand your A copy could already throw because it had alloc failure.
Currently you would need to handle the destruction because you have not concreted the array that you have allocated, and all the T instances that you have created need to be destroyed if one of them exceptions, perhaps due to allocation failure.
One quick way to fix that would be to hold the array in a unique_ptr. Then it will be destroyed on exiting context.
Another way may be to reconsider your contract on A after the assignment has exceptioned: It must be valid, i.e. survive being used, but perhaps it need not guarantee to still contain its previous contents, nor all the new contents, so you could decide to destroy its existing array before allocating and assigning a new array, then copying the members. You could decide not to reallocate if the size has not changed, but just re-assign - this would leave a mess of new and old members after an exception, but they would all be valid and safe to delete.
Please ensure that size matches the actual attached array size at all times! Your existing code makes this mistake, but in particular that it is set to null and 0 after the delete and before the assignment; and it is only set to new new size after the assignment of the new pointer.

Inserting Objects into an array with dynamic memory (No vectors allowed) C++

I am not allowed to use Vectors specifically for this school assignment. Most of the answers I've found simply state "you should use vectors" as the most up-voted comment. While I appreciate and understand this, I'm simply restricted from using them for this assignment.
It is a C++ assignment with dynamic memory management as follows:
// property in header declaration
int numAnimals;
int capacity;
Animal** animals;
void addAnimal(Animal *newAnimal);
// class implementation
capacity = 10;
numAnimals = 0;
animals = new Animal*[capacity];
void SampleClass::addAnimal(Animal *newAnimal)
{
for (int i = 0; i < capacity; i++){
if(animals[i]){
// animal object already exists in array, move on
i++;
}else{
animals[i] = newAnimal;
numAnimals++;
break;
}
}
}
animals is a pointer to a pointer, in this case a pointer to an array of pointers to object type Animal which have yet to be created.
With the 'addAnimal' function, what I'm trying to do is add an animal object to the array by looping through the array of pointers, and if there already exists an animal object, iterate to the next index. If there is no animal, then insert that animal into the array.
I'm getting an exception thrown "read access violation" when I attempt to access a member function of an animal object in the array.
My suspicion is because:
if(animals[i]) probably isn't doing what I think it's doing, running it through the debugger I never hit the 'else' portion, so the array is still full of pointers not set to any objects when the method is complete. Therefore, when I try to access a member function, it's of an object that doesn't exist.
So, if my suspicions are correct, then what is the best way to insert a new object into an array of pointers in this fashion? They need to be pointers otherwise it automatically creates the array full of populated objects, which is not what I want.
I didn't post all of the code because I wanted to keep my problem brief, apologies I'm a new to C++ and stackoverflow in general. Yes, I know to delete[] to clear memory afterwards.
Any help is appreciated, thanks!
Since in numAnimals you keep count of the current number of animal pointers in the array, you don't need the for loop to find the first available slot to add a new animal pointer (note also that, assuming you want to use a for loop like shown in your code, you have to pay attention to properly initialize all the initial pointers in the array to nullptr).
You can just use:
// Inside SampleClass::addAnimal(Animal *newAnimal):
animals[numAnimals] = newAnimal;
numAnimals++;
Please note that you have to pay attention to not overflow your array capacity, when you insert a new animal.
So, before inserting a new animal, you have to check that there's enough room in the array, e.g.:
// Before inserting:
if (numAnimals == capacity)
{
// You ran out of capacity.
//
// 1. Allocate a new array with bigger capacity (e.g. 2X)
// 2. Copy the content from the current array to the new one
// 3. delete current array
}
As a side note:
Yes, I know to delete[] to clear memory afterwards
Note that if you call delete[] on the animals array of pointers, you release this pointer array, but not the Animal objects pointed to.
int capacity = 10;
Animal** animals = new Animal*[capacity];
This allocates ten pointers, except it does no initialization,
meaning you have essentially garbage data.
if (animals[i])
This test one of those pointers against nullptr, but since you did
no initialization, it's not very likely that it's nullptr.
You need to add a loop-pass that null out the data, right after you
allocate it:
for(int i=0; i<capacity; ++i)
animals[i] = nullptr;
There is also a bug:
if (animals[i]) {
// animal object already exists in array, move on
i++; // <- here
This is wrong, you move on twice for (int i = 0; i < capacity;i++)
The thing is "Use a vector", despite your teacher telling you otherwise, is the correct way to do it. However, if you are supposed to manage the dynamic array manually, then the best you can do is to encapsulate all that dirty memory stuff inside a class and write your own replacement for std::vector. Ie to avoid having dynamic allocations spread all over your code (especially in places that are supposed to deal with Animals and shouldnt care about manually allocating memory and the like) you should write a class that does that (and nothing else) and provides a nicer interface. I can only outline the idea here:
template <typename T>
class my_vector {
private:
T* data;
size_t size;
size_t capacity;
public:
void push_back(const T& t);
size_t size();
T& operator[](size_t index);
void resize(size_t size);
//... etc...
};

Dynamic memory allocation in c++ without a variable [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I am learning the dynamic memory allocation process in c++.
1.How to declare dynamic memory allocation of an array without prior knowledge of the size?
2.Suppose I use a variable to dynamically allocate memory to the array but later on in the program the size of array is reduced.Will there be automatic de-allocation of memory?
If not then how to update it?
Please spare me if these are silly questions.If you answer please include an example program.
As Cheers and hth said in the comments, the best way is to use std::vector, it takes care of the memory management itself.
How to declare dynamic memory allocation of an array without prior knowledge of the size?
The idea is, you do not allocate any memory if you do not know the size, and when adding elements to it, you can increase the memory, see example below.
Implementing a class that works like std::vector:
vector uses two sizes, one is the size which is the number of elements your vector is currently holding, capacity is the number of elements which your vector can hold (memory is allocated for capacity)
Pre-requisites: I'm assuming that you know the basic memory allocation and de-allocation, that you can allocate memory using new operator, and de-allocate using delete operator.
Note: I'm implementing some methods for MyIntVector that uses only int array for simplicity, but you can always implement a templated class.
Implementation:
You can provide a default constructor for your class, which doesn't allocate any memory, sets the size and capacity to 0 (client programs can use this constructor, if the size is not known).
You can also provide a constructor which takes a size that will be used as the starting size, when creating the MyIntVector
Also provide a destructor to completely de-allocate the allocated memory.
class MyIntVector{
size_t _size;
size_t _capacity;
int* _data;
public:
MyIntVector(){
_size = 0;
_capacity = 0;
_data = nullptr;
}
MyIntVector(size_t size){
_size = size;
_capacity = size;
_data = new int[_capacity];
}
~MyIntVector(){
if (_data){
delete[] _data;
}
_data = nullptr;
_size = _capacity = 0;
}
};
Now, if you want to add some element to your MyIntVector, you can implement a push_back function, like std::vector does.
class MyIntVector{
//...
void push_back(int elem){
if (_size >= _capacity){
// Oops, we're out of memory, let's make room for this elem first
resize(_capacity > 0? _capcity * 2 : 10);
}
// Now, there's enough memory to hold this elem
_size++;
_data[_size - 1] = elem;
}
void resize(size_t newCapacity){
if (newCapacity != _capacity){
if (_size == 0){
_capacity = newCapacity;
_data = new int[_capacity];
}
else {
// create a temporary array to hold the elements of _data
int* temp = new int[_size];
for (size_t i = 0; i < _size; i++)
temp[i] = _data[i];
// de-allocate the memory of _data
delete[] _data;
// allocate memory in _data with newCapacity
_capacity = newCapacity;
_data = new int[_capacity];
// copy the elements of temporary array back in _data
for (size_t i = 0; i < _size; i++){
_data[i] = temp[i];
}
// done with the temporary array, de-allocate it.
delete[] temp;
temp = nullptr;
}
}
}
//...
};
push_back function:
The push_back function, that I've implemented in above example, it sees whether the new element can be added to the MyIntVector without any need to allocate any new memory or not, if not, it just increases the _size by 1 and copies the element. If there's a need for new memory, it calls the resize method with doubled capacity (in case, there's already some allocated memory there.) or some hard-coded value, say 10 (in case, where there's not any previously allocated memory)
resize function:
The resize function takes a newCapacity as argument, and allocates the required memory in _data, mainly it creates a temporary array to hold the elements of _data and then de-allocates the memory in _data, allocates a larger memory in _data, copies back the from temp to _data and de-allocates the temp array.
Note: This implementation of resize should only be used when increasing the _capacity, it should not be called for decreasing the _capacity
So, the idea is, that you can increase the size of the array, by using a temporary array for holding the elements, de-allocating the previous array, allocating a new array, and copying back the elements from temporary array. You can implement different methods that std::vector provides for your exercise.
If you don't want to de-allocate and re-allocate the memory every time you increase the capacity, you can always implement a Linked List, here's a tutorial for implementing a Linked List, but there's a drawback of Linked List, that you can't randomly access elements from the Linked List like you can from an array.
Suppose I use a variable to dynamically allocate memory to the array but later on in the program the size of array is reduced.Will there be automatic de-allocation of memory?
There's no automatic de-allocation of memory, you'll have to manually decrease the _capacity if you want.
You can add a pop_back method in your MyIntVector class to remove an element.
class MyIntVector{
// ...
int pop_back(){
if (_size == 0){
// You can throw some exception here if you want.
throw exception("No elements found");
}
_size--;
return _data[_size];
}
// ...
};
You can manually decrease the _capacity before returning the element.
You can also provide an implementation of subscript operator [] for your MyIntVector class to provide random access in the array.
If you do choose to dynamically allocate memory, you will need to use the operator new[] for an array, which is considered a different operator in c++ than new for non-array types.
1) Here is an example of how to dynamically allocate an array:
int length = 10;
int *myArray = new int[length];
// do something
myArray[0] = 42;
When you are done, you will need to release the memory with delete[]:
delete[] myArray;
2) No, there is no automatic de-allocation of dynamically allocated memory in C++ unless you are using smart pointers (What is a smart pointer and when should I use one?).
If you want to resize your array, you will have to do it manually:
delete[] myArray;
int newLength = 5;
myArray = new int[newLength];
As #frameworks pointed out, you will need the operator new[] to allocate memory for an array and you need to call delete[] to free that memory. If there is no delete[] for a new[], your program will leak memory. (If there is more than one delete[] called for a single new[], your program will segfault / crash.)
However, it is not always trivial to ensure that memory allocated with new[] (or new) gets always cleaned up by a corresponding delete[] (or delete), and only gets cleaned up once. That is especially true for code with complex control flow, code where pointers get passed around or situations where exceptions can be thrown between new[] and delete[].
In other words: Wherever there is a new, there probably is a leak.
So to save you all that trouble of manual memory management, use std::vector instead. This container takes care of the memory management for you.
Whenever you are tempted to write something like
int *myArray = new int[3];
//do something with myArray
delete[] myArray;
you should write
std::vector<int> myArray;
//do something with myArray
instead. You do not need a delete here and methods like std::vector::push_back() take care of the required size adjustments for the underlying structure, should it not provide enough space to accomodate all pushed values. You can also use std::vector::shrink_to_fit() to remove unused elements. (However, shrink_to_fit is a non-binding request to reduce the internal size. That is, results may vary between compilers.)

Simple implementation of vector class like std::vector

I'm implementing a simple vector like std::vector, and I wrote some functions , without worrying what kind of exception safe guarantee it gives. I know a little about exceptions of c++, but i have not experienced writing exception safe codes.
Here is my code :
template <typename T>
class vector {
public:
vector(int _DEFAULT_VECTOR_SIZE = 10) :
size(0), array_(new T[_DEFAULT_VECTOR_SIZE]), capacity(_DEFAULT_VECTOR_SIZE) {}
void push_back(T& elem)
{
if (size == capacity)
resize(2 * size);
array_[size] = elem;
size++;
}
void pop_back()
{
--size;
}
void resize(int size_)
{
if (size_ > capacity)
{
T* temp = new T[size_];
memcpy(temp,array_,size*sizeof(T));
swap(temp, array_);
delete[] array_;
capacity = size_;
}
}
private:
T* array_;
int size;
int capacity;
};
So my question is: How can I modify my code(functions) , that would give at least basic guarantee , or some technique of writing exception safe code for basic or strong guarantee ?
Thanks
Exception-safety comes in two major flavours:
If an exception happens, your program ends in a sane state. Which one is unspecified.
If an exception happens, your program ends in the original state.
The main challenge for you will be to deal with assignment and copy constructors throwing. As the comments already note, you shouldn't use memcpy because that fails to call copy constructors. Copying a std::string should also copy the character buffer, for instance, and a vector of strings is a perfectly normal type which you should support.
So, let's look at your vector's copy constructor. It will need to copy every element of the source vector. And each individual copy can throw. What if one of the strings is so long that a copy throws std::bad_alloc ?
Now, exception safety means that you leave the program in a sane state, so no memory leaks. Your vector's copy ctor failed, so the dtor won't run. Who cleans up T* array then? This must be done in your copy ctor.
When the copy fails, there won't be a new vector, so you get the second type of exception safety for free. ("strong exception safety"). But let's look at the assignment operator next, v2 = v1. There's an old vector that you will overwrite. If you first do a .resize(0) and then copy over all elements, you may encounter an exception halfway through the copy. Your original vector content is gone, and the new content is incomplete. Still, you haven't leaked any memory yet nor have you copied half an element.
To make the assignment safe, there's a simple trick: first copy the source vector to a temporary vector. If this fails, no problem (see above). We didn't touch the destination yet. But if the assignment succeeds, we swap the array* pointers, size and capacity of the temporary and the destination vectors. Swapping pointers and swapping ints is safe (can't throw). Finally, we let the temporary vector go out of scope which destroys the old vector elements that are no longer needed.
So, by doing all the dangerous operations on temporaries, we ensure that any exception doesn't touch the original state.
You'll need to check all your methods to see if these problems can occur, but the pattern is usually similar. Don't leak array if an element copy or element assignment throws, do propagate that exception to your caller.