Подтвердить что ты не робот

Почему добавление деструктора изменяет поведение конструктора копии этой структуры?

У меня есть немного кода, который я считаю запутанным. В частности, когда я пытаюсь добавить что-то в список в качестве списка инициализаторов - он работает до тех пор, пока я не добавлю деструктор, - тогда он начинает пытаться найти конструктор копии.

Это не похоже на вполне последовательное поведение. Возьмите этот минимальный пример:

#include <list>
int main()
{
    class MemberType
    {
    public:
        MemberType()  {}
        MemberType(MemberType&& copy) { }
    };
    struct ListItemType
    {
        MemberType x;
        ~ListItemType() {}
    };
    std::list<ListItemType> myList;
    myList.push_back({MemberType()});
    return 0;
}

Это не скомпилируется в GCC и VS2015, потому что push_back пытается получить доступ к конструктору копирования ListItemType:

main()::ListItemType::ListItemType(const main()::ListItemType&)

(согласно моему пониманию). Это, по-видимому, имеет смысл, поскольку список push_back сделает копию (так как нет конструктора перемещения), за исключением того, что это не поведение, если вы удалите деструктор. Прокомментируйте деструктор, и компиляция завершится успешно, как и ожидалось.

Тем не менее, нижеследующее прекрасно работает даже с деструктором - для его удовлетворения не нужны конструкторы копирования или перемещения. Это похоже на то же поведение для меня, хотя.

ListItemType foo = { MemberType() };

Наконец, если вы удалите или закомментируете конструктор перемещения MemberType - компиляция снова завершится успешно, то есть следующее будет компилироваться.

#include <list>
int main()
{
    class MemberType
    {
    public:
        MemberType()  {}
    };
    struct ListItemType
    {
        MemberType x;
        ~ListItemType() {}
    };
    std::list<ListItemType> myList;
    myList.push_back({MemberType()});
    return 0;
}

Может кто-нибудь объяснит здесь поведение? Почему push_back пытается получить доступ к конструктору копирования ListItemType - но только если ListItemType имеет деструктор, а MemberType имеет конструктор перемещения?

4b9b3361

Ответ 1

Поведение, которое вы наблюдаете, генерируется правилами, определяющими, генерируются ли компилятором неявные экземпляры копирования или перемещения:

Неявное перемещение

Если не определено, конструктор перемещения неявно объявлен для класса if:

  • класс не имеет определенных пользователем конструкторов копирования; и
  • класс не имеет назначенного пользователем назначения копирования или переводит операторы присваивания; и
  • класс не имеет определяемого пользователем деструктора.

Неявная копия

Если не определено, конструктор копирования неявно удален для класса, если:

  • класс имеет определяемый пользователем конструктор перемещения; или
  • другие причины, которые здесь не актуальны...

В вашем вопросе у вас есть несколько случаев:

Случай 1

  • ListItemType имеет деструктор
  • MemberType имеет конструктор перемещения

Неявный конструктор перемещения для ListItemType был удален из-за существования деструктора. Таким образом, push_back должен использовать конструктор копирования, чтобы поместить ListItemType в список.

В этом случае конструктор копирования для ListItemType не может быть неявно объявлен как один из его членов данных (MemberType) содержит конструктор перемещения, который предотвращает создание неявного конструктора копии для MemberType.

Случай 2

  • ListItemType не имеет деструктора
  • MemberType имеет конструктор перемещения

Конструктор перемещения может быть неявно сгенерирован для ListItemType и используется для перемещения значения в список.

Случай 3

  • ListItemType имеет деструктор
  • MemberType не имеет конструктора перемещения

Неявный конструктор копирования для ListItemType и MemberType может быть сгенерирован и использован для копирования значения в список.

Наконец, выражение ListItemType foo = { MemberType() }; является агрегатной инициализацией и следует различным правилам. В любом случае MemberType будет иметь либо конструкцию перемещения, либо копию, достаточную для агрегатной инициализации.