dimanche 26 juillet 2020

std::vector reallocations in transactional way: copy and move constructors

In this question I want to clarify some details about std::vector reallocations in C++11 and further. I have a class Data with all defined contructors and I'm putting objects of this class to std::vector:

#include <iostream>
#include <vector>

struct Data
{
    Data()
    {
        std::cout << "[Data]\n";
    }
    explicit Data(int v)
        : ptr(new int(v))
    {
        std::cout << "[Data] v: " << v << "\n";
    }

    ~Data()
    {
        if (ptr)
            delete ptr;
        std::cout << "[~Data]\n";
    }

    Data(const Data& rhs)
    {
        std::cout << "[Data] copy cntr\n";
        *this = rhs;
    }

    Data& operator=(const Data& rhs)
    {
        if (this != &rhs)
        {
            if (rhs.ptr)
                ptr = new int(*rhs.ptr);
        }

        return *this;
    }

    Data(Data&& rhs) noexcept
    {
        *this = std::move(rhs);
        std::cout << "[Data] move cntr\n";
    }

    Data& operator=(Data&& rhs) noexcept
    {
        if (this != &rhs)
        {
            ptr = rhs.ptr;
            rhs.ptr = nullptr;
        }

        return *this;
    }

private:
    int* ptr = nullptr;
};

int main()
{
    std::vector<Data> values;
    values.emplace_back(1);
    values.emplace_back(2);
    values.emplace_back(3);
    values.emplace_back(4);

    std::cout << "------\n";
    return 0;
}

After the execution we have the following output:

[Data] v: 1
[Data] v: 2
[Data] move cntr
[~Data]
[Data] v: 3
[Data] move cntr
[Data] move cntr
[~Data]
[~Data]
[Data] v: 4
------
[~Data]
[~Data]
[~Data]
[~Data]

Everything is clear here. During vector Reallocations move_if_noexcept is called under ther hood because move constructor is noexcept:

Data(Data&& rhs) noexcept

Move operation can be done transactionally. If I delete noexcept:

Data(Data&& rhs)

I will have the following output:

[Data] v: 1
[Data] v: 2
[Data] copy cntr
[~Data]
[Data] v: 3
[Data] copy cntr
[Data] copy cntr
[~Data]
[~Data]
[Data] v: 4
------
[~Data]
[~Data]
[~Data]
[~Data]

Copy constructor will be called (fallback to copy) to guarantee vector reallocation that can be done transactionally. And the moment that I really do not understand. If I explicitly delete copy constructor and assigment:

Data(const Data& rhs) = delete;
Data& operator=(const Data& rhs) = delete;

The output will be the following in spite of the fact that move construct in not noexcept:

[Data] v: 1
[Data] v: 2
[Data] move cntr
[~Data]
[Data] v: 3
[Data] move cntr
[Data] move cntr
[~Data]
[~Data]
[Data] v: 4
------
[~Data]
[~Data]
[~Data]
[~Data]

Does it means that in this way we do not have any guarantees about reallocations that can be done transactionally (move constructor can throw exception and vector will be in invalid state)? Is it ok? My expectation is to get compile error in this case.

Aucun commentaire:

Enregistrer un commentaire