C++ Queue::Pop function acts weird when compiled with -O1

Viewed 168

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.

1 Answers

Ok with the help of Jason turner and Bjarne Stroustrup on linkedin I was able to find the problem.

The problem is using Span to hold the data! When Span goes out of scope it will call all of the destructors of the array objects despite the fact not all of them are constructed and need to destructed which will cause double free-ing. To solve this I wrote a Memory holder class which only does malloc(new) and free(delete) and in the BQueue's destructor it disposes only the objects from _First to _First + _Length and not others which are not constructed.

Related