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

У меня есть интерфейс IList<T>, поддерживающий операции добавления копированием и перемещением:
Шаблон IList
namespace lib {
    template<class T>
    class IList {
    public:
        virtual void push(const T & data) = 0;
        virtual void push(T && data) = 0;

        virtual ~IList() = default;
    };
}


И есть класс LinkedList<T>, который этот интерфейс реализует. Логика обычного односвязного списка: есть внутренний класс Node, который просто хранит данные и который также поддерживает как конструктор копирования, так и конструктор перемещения, и два метода push: один копирует передаваемые данные, другой их перемещает, то есть во втором случае копии T создаваться не будет.
Шаблон LinkedList
namespace lib {
    template<class T>
    class LinkedList : public IList<T> {
    private:
        class Node { // внутренний класс листа
        public:
            Node* next = nullptr;
            T data;

        public:
            explicit Node(const T & data) : data(data) {}; // копирование
            explicit Node(T && data) : data(std::move(data)) {}; // перемещение
        };

    private:
        Node* head;
        Node* tail;
        int len;
    public:
        LinkedList() :
            head(nullptr),
            tail(nullptr),
            len(0) {}; // конструктор листа 

        void push(const T & data) {
            std::cout << "LinkedList::push(const &)" << std::endl;
            Node* tmp = new Node(data); // вызывает копирующий конструктор Node
            if (this->tail == nullptr) {
                this->head = this->tail = tmp;
            } else {
                this->tail->next = tmp;
                this->tail = tmp;
            }
            ++len;
        }

        void push(T && data) {
            std::cout << "LinkedList::push(&&)" << std::endl;
            Node* tmp = new Node(std::move(data)); // вызывает конструктор перемещения Node
            if (this->tail == nullptr) {
                this->head = this->tail = tmp;
            } else {
                this->tail->next = tmp;
                this->tail = tmp;
            }
            ++len;
        }

    };
}


Также у меня есть класс Item, для которого запрещены любые операции копирования
Шаблон Item
template<class Data>
class Item {
private:
    int version;

public:
    Item() = default;

    Item(int version) : version(version) {}


private:
    Item(const Item & item) = default;
    Item& operator=(const Item & item) = default;

public:
    Item(Item && item) = default;
    Item& operator=(Item && item) = default;
};


По задумке, для объекта типа
lib::LinkedList<Item<int>> list = lib::LinkedList<Item<int>>()
мы будем обязаны вызвать list.push() только для r-value значения, в ином случае выкинется ошибка. Но просто при вызове конструктора lib::LinkedList<Item<int>>() компилятор уже выкидывает ошибку
lib/LinkedList.h: In instantiation of 'lib::LinkedList<T>::Node::Node(const T&) [with T = Item<int>]':
lib/LinkedList.h:35:25:   required from 'void lib::LinkedList<T>::push(const T&) [with T = Item<int>]'
main.cpp:24:1:   required from here
lib/LinkedList.h:16:54: error: 'Item<Data>::Item(const Item<Data>&) [with Data = int]' is private within this context
     explicit Node(const T & data) : data(data) {};
                                                                     ^
In file included from src/KeySpace1.h:6:0,
                 from main.cpp:6:
src/Item.h:20:5: note: declared private here
     Item(const Item & node) = default;
     ^~~~


То есть он говорит, что не может создать list, потому что при его создании вызывается explicit Node(const T & data) : data(data) {}, который в свою очередь выполняет операцию копирования T data (T = Item<int>). Но почему этот конструктор вообще вызывается? При вызове конструктора lib::LinkedList<Item<int>>() я вообще не вызываю ни один конструктор Node.

Самое забавное, что если закомментировать в IList объявления virtual void push(const T & data) = 0 и virtual void push(T && data) = 0, то ошибки не возникает.
Что вообще происходит?
  • Вопрос задан
  • 224 просмотра
Решения вопроса 1
wataru
@wataru Куратор тега C++
Разработчик на С++, экс-олимпиадник.
Он там не вызывается. Этот приватный конструктор используется в функции
void lib::LinkedList<T>::push(const T&) [with T = Item<int>]
, которая в шаблоне (там где вы создаете Node* tmp). При первом обращении к этой функции компилятор пытается ее инстанциировать и натыкается на эту проблему, о чем и сообщает вам. Пока эта функция вообще никак нигде не используется - программа компилируется.

В приведенных вами примерах вы эту функцию так или иначе трогаете разными способами.

lib::LinkedList<Item<int>> list = lib::LinkedList<Item<int>>()
вызвает проблему пока у вас есть virtual метод в интерфейсе, потому что это тут вызывается конструктор копирования (сначала конструктор по умолчанию для временного lib::LinkedList<Item<int>>(), а потом это все копируется в конструируемый list).

Этот конструктор копирования должен будет также скопировать таблицу виртуальных вызовов и тут компилятору понадобится знать про запретный метод push. Похоже. Я не уверен точно, почему это проявляется именно в конструкторе копирования, где-то в недрах стандарта про это, наверняка, написано.

Если вы напишите lib::LinkedList<Item<int>> list() - то все скомпилируется, потому что конструктор по умолчанию, видимо, не требует знания о методе push. Также удаление virtual или наследования вылечит эту проблему.

Далее, точно по этой же причине не скомпилируется list.push(a) - это прямой вызов этой запретной функции. Если же вы напишите list.push(Item<int>()), то оно скомпилируется потому что тут вызывается push с перемещением.

Но для исправления этого кода вам надо прежде всего избавится от этой поломанной функции push.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@DefiniteIntegral
Нужно просто добавить модификатор default в конструктор, чтобы его можно было вызывать из класса, но не из другого пакета
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы