I have been writing my own queue in c++. The problem is that it has a function called Queue::Pop() which calls the destructor of the first item in the queue which is kept track of by an index called _First, and afterwards _First is incremented and _Length is decremented. When normally compiled, it performs as expected, however when -O1 flag is added, it starts acting weird and does not call the object's destructor properly. The printing in destructor happens but the Id is not set to -1. and as result, when the queue goes out of the scope, the memory is delete[] ed and destructors are called again and that is invalid code since if it was a file descriptor, it would be closed twice. here is the Pop function:
#include <iostream>
#include <tuple>
#include <memory>
#include <sys/uio.h>
#include <initializer_list>
namespace Core
{
namespace Iterable
{
template <typename T>
class Span
{
private:
size_t _Length = 0;
T *_Content = nullptr;
inline T &_ElementAt(size_t Index)
{
return this->_Content[Index];
}
inline const T &_ElementAt(size_t Index) const
{
return this->_Content[Index];
}
public:
Span() = default;
Span(size_t Size) : _Length(Size), _Content(new T[Size]) {}
Span(size_t Size, const T &Value) : _Length(Size), _Content(new T[Size])
{
for (size_t i = 0; i < _Length; i++)
{
_Content[i] = Value;
}
}
Span(Span &&Other) : _Length(Other._Length), _Content(Other._Content)
{
Other._Content = nullptr;
Other._Length = 0;
}
Span(const Span &Other) : _Length(Other._Length), _Content(new T[Other._Length])
{
for (size_t i = 0; i < Other._Length; i++)
{
_Content[i] = Other._Content[i];
}
}
Span(const T *Array, size_t Size) : _Length(Size), _Content(new T[Size])
{
for (size_t i = 0; i < Size; i++)
{
_Content[i] = Array[i];
}
}
Span(std::initializer_list<T> list) : _Length(list.size()), _Content(new T[list.size()])
{
size_t i = 0;
for (auto &item : list)
{
_Content[i] = item;
i++;
}
}
~Span()
{
delete[] _Content;
_Content = nullptr;
}
inline T *Content()
{
return _Content;
}
inline const T *Content() const
{
return _Content;
}
inline size_t Length() const
{
return _Length;
}
T &operator[](const size_t &Index)
{
if (Index >= _Length)
throw std::out_of_range("");
return _ElementAt(Index);
}
const T &operator[](const size_t &Index) const
{
if (Index >= _Length)
throw std::out_of_range("");
return _ElementAt(Index);
}
Span &operator=(const Span &Other)
{
if (this != &Other)
{
_Length = Other._Length;
delete[] _Content;
_Content = new T[_Length];
for (size_t i = 0; i < _Length; i++)
{
_Content[i] = Other._Content[i];
}
}
return *this;
}
Span &operator=(Span &&Other)
{
if (this != &Other)
{
delete[] _Content;
_Content = Other._Content;
_Length = Other._Length;
Other._Content = nullptr;
Other._Length = 0;
}
return *this;
}
};
template <typename T>
class BQueue final
{
public:
// Constructors
BQueue() = default;
BQueue(size_t Size, bool Growable = true) : _Content(Size), _First(0), _Length(0), _Growable(Growable) {}
BQueue(std::initializer_list<T> list) : _Content(list), _First(0), _Length(list.size()), _Growable(true) {}
BQueue(const BQueue &Other) : _Content(Other._Content), _First(Other._First), _Length(Other._Length), _Growable(Other._Growable) {}
BQueue(BQueue &&Other) : _Content(Other._Content), _First(Other._First), _Length(Other._Length), _Growable(Other._Growable)
{
Other._First = 0;
Other._Length = 0;
Other._Growable = true;
}
// Operators
BQueue &operator=(const BQueue &Other)
{
if (this != &Other)
{
_Content = Other._Content;
_First = Other._First;
_Length = Other._Length;
_Growable = Other._Growable;
}
return *this;
}
BQueue &operator=(BQueue &&Other)
{
if (this != &Other)
{
_Content = std::move(Other._Content);
_First = std::move(Other._First);
_Length = std::move(Other._Length);
_Growable = std::move(Other._Growable);
Other._First = 0;
Other._Length = 0;
Other.Growable = true;
}
return *this;
}
T &operator[](size_t Index)
{
if (Index >= _Length)
throw std::out_of_range("Index out of range");
return _Content.Content()[(_First + Index) % Capacity()];
}
T const &operator[](size_t Index) const
{
if (Index >= _Length)
throw std::out_of_range("Index out of range");
return _Content.Content()[(_First + Index) % Capacity()];
}
// Peroperties
size_t Capacity() const
{
return _Content.Length();
}
size_t Length() const
{
return _Length;
}
bool Growable() const
{
return _Growable;
}
T *Content()
{
return _Content.Content();
}
T const *Content() const
{
return _Content.Content();
}
inline bool IsWrapped() const
{
return _First + _Length > Capacity();
}
inline bool IsEmpty() noexcept { return _Length == 0; }
inline bool IsFull() noexcept { return _Length == Capacity(); }
inline size_t IsFree() noexcept { return Capacity() - _Length; }
// Helper functions
T &Head()
{
AssertNotEmpty();
return _Content.Content()[_First];
}
T const &Head() const
{
AssertNotEmpty();
return _Content.Content()[_First];
}
// Remove functionality
void Pop()
{
std::destroy_at(std::addressof(Head()));
--_Length;
_First = (_First + 1) % Capacity();
}
private:
Iterable::Span<T> _Content;
size_t _First = 0;
size_t _Length = 0;
bool _Growable = true;
inline void AssertNotEmpty()
{
if (IsEmpty())
throw std::out_of_range("Instance is empty");
}
};
}
}
class Messenger
{
public:
int Id = -1;
Messenger() = default;
Messenger(size_t id) : Id(id)
{
std::cout << Id << " Constructed" << std::endl;
}
Messenger(Messenger &&Other) : Id(Other.Id)
{
Other.Id = -1;
}
Messenger(Messenger const &Other) : Id(Other.Id) {}
Messenger &operator=(Messenger &&Other)
{
Id = Other.Id;
Other.Id = -1;
return *this;
}
Messenger &operator=(Messenger const &Other)
{
Id = Other.Id;
return *this;
}
~Messenger()
{
if (Id != -1)
{
std::cout << Id << " Destructed" << std::endl;
Id = -1;
}
}
};
using namespace Core;
int main(int argc, char const *argv[])
{
Iterable::BQueue<Messenger> Queue{1, 2};
Queue.Pop();
Queue.Pop();
std::cout << "Finished" << std::endl;
return 0;
}
And here the output that i get:
1 Constructed
2 Constructed
1 Destructed
2 Destructed
Finished
2 Destructed
1 Destructed
As you can see, after printing Finished, nothing else must be printed.