1

So I tried to make a custom string & vector class (from a youtube channel named The Cherno). But when I tried to make a copy constructor on that vector class with my custom string class, I get this exception: str was nullptr (Occured in the string copy constructor) and also when I tried with a primitive data types (int) I also get this exception: std::move<int & __ptr64>(...) returned nullptr (Occured on the re_allocate function).

main.cpp:

int main() {
    utils::list<int> list;
    list.place(5);
    utils::list<int> other = list; // Works fine if I remove this line
}

String copy constructor:

utils::string::string(const string& other)
    : pr_Length(other.pr_Length)
{
    // If the other string was not initialized then return
    if (!other.pr_Init) {
        pr_Init = false;
        return;
    }

    // Allocates a new char*
    pr_Data = new char[pr_Length + 1];
    // Copy all the char* on other string and copy it here
    std::memcpy(pr_Data, other.pr_Data, pr_Length + 1);

    // This string is initialized
    pr_Init = true;
}

Vector class:

template<typename T>
    class list {
    private:
        T* pr_Array;
        size_t pr_Size;
        size_t pr_Cap;  
    public:
        using ValType = T;
        using Iterator = list_iterator<list<T>>;

        list() {
            re_allocate(2);
        }
        ~list() {
            destroy_all();

            ::operator delete(pr_Array, pr_Cap * sizeof(T));
        }
        list(const list& other)
            : pr_Size(other.pr_Size), pr_Cap(other.pr_Cap)
        {
            re_allocate(pr_Cap);
        }

        void add(const T& elem) {
            if (pr_Size >= pr_Cap) {
                re_allocate(pr_Cap + 2);
            }

            new(&pr_Array[pr_Size]) T(std::move(elem));
            pr_Size++;
        }
        void add(T&& elem) {
            if (pr_Size >= pr_Cap) {
                re_allocate(pr_Cap + 2);
            }

            new(&pr_Array[pr_Size]) T(std::move(elem));
            pr_Size++;
        }
        template<typename...Args>
        T& place(Args&&... args) {
            if (pr_Size >= pr_Cap) {
                re_allocate(pr_Cap + 2);
            }

            new(&pr_Array[pr_Size]) T(std::forward<Args>(args)...);
            return pr_Array[pr_Size++];
        }
        void destroy_back() {
            if (pr_Size == 0) {
                return;
            }

            pr_Array[pr_Size].~T();
            pr_Size--;
        }
        void destroy_all() {
            for (size_t i = 0; i < pr_Size; i++) {
                pr_Array[i].~T();
            }
            pr_Size = 0;
        }

        const T& operator[](size_t i) const {
            return pr_Array[i];
        }
        T& operator[](size_t i) {
            return pr_Array[i];
        }

        const size_t size() const {
            return pr_Size;
        }
        size_t size() {
            return pr_Size;
        }
        Iterator begin() {
            return Iterator(pr_Array);
        }
        Iterator end() {
            return Iterator(pr_Array + pr_Size);
        }
    private:
        void re_allocate(size_t cap) {
            T* newBlock = (T*)::operator new(cap * sizeof(T));

            if (cap < pr_Size) {
                pr_Size = cap;
            }

            for (size_t i = 0; i < pr_Size; i++) {
                new(newBlock + i) T(std::move(pr_Array[i]));
            }
            for (size_t i = 0; i < pr_Size; i++) {
                pr_Array[i].~T();
            }

            ::operator delete(pr_Array, pr_Cap * sizeof(T));
            pr_Array = newBlock;
            pr_Cap = cap;
        }
    };
  • You failed to implement an [overloaded assignment operator](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three). – PaulMcKenzie Apr 17 '22 at 02:49
  • @PaulMcKenzie `main` uses only the copy constructor. The copy constructor is however not implemented correctly. It sets size and capacity and then tries to reallocate to a new capacity (??). It never copies the actual elements from the other list. – user17732522 Apr 17 '22 at 02:52
  • I know what the current toy version of `main` is doing. Just wanted to point out that an even more toyish program breaks his code – PaulMcKenzie Apr 17 '22 at 02:54

1 Answers1

2
    utils::list<int> list;

main() default-constructs an instance of this template. Let's inspect the template and its default constructor, then.

        T* pr_Array;
        size_t pr_Size;
        size_t pr_Cap;  

This template class declares these members.

        list() {

The constructor does not have a member initialization section, and these three members do not have a default value, so all of these three class members are not initialized to anything. Their initial value is random garbage.

            re_allocate(2);

The constructor then calls re_allocate(2).

            if (cap < pr_Size) {

re_allocate, at this point, compares its parameter, cap, against pr_Size. But if you've been keeping careful notes, you will then note that pr_Size was not initialized to anything. From this point on, everything is undefined behavior.

There may or may not be additional instances of undefined behavior in this tempalte class, but given that this undefined behavior always occurs in the constructor, no further analysis is possible to determine that.

For example, pr_reallocate refers to pr_Array too. The current state of affairs is that pr_Array is also uninitialized and contains random garbage, so there's that as well. Whether or not this remains undefined behavior depends on how the initial occurence of undefined behavior gets fixed.

Then there's potential undefined behavior in the copy-constructor... But, one step at a time...

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148