Я провожу «эксперименты» с семантикой перемещения, чтобы полностью ее понять. Я дошел до того, что не совсем понимаю. Это связано с тем, что кажется, что существует конфликт с конструктором, подобным A::A(B b) и A::A(B &&b) при использовании std::move. Ниже приведен код, объясняющий это:

struct Item {
    Item() { }

    Item(const Item &other) {
        std::cout << "Item::Item(const Item& )\n";
    }
};

struct Container {
    Container(Item i) {
        std::cout << "Container::Container(Item )\n";
    }

    Container(Item &&i) {
        std::cout << "Container::Container(Item&& )\n";
    }
};

int main() {
    Item i;
    Container c(std::move(i));
    return 0;
}

При попытке скомпилировать приведенный выше код отображается следующая ошибка:

error C2668: 'Container::Container' : ambiguous call to overloaded function
1>          c:\users\xe74139\documents\visual studio 2012\projects\project1\project1\maincpp.cpp(21): could be 'Container::Container(Item &&)'
1>          c:\users\xe74139\documents\visual studio 2012\projects\project1\project1\maincpp.cpp(17): or       'Container::Container(Item)'

Я знаю, что конструктор Container(Item i) вообще не имеет смысла, но я до сих пор не понимаю, почему компилятор C ++ видит конфликт между обоими конструкторами Container.

Почему он не может определить, что Container c(i) предназначен для вызова Container(Item), а Container c(std::move(i)) предназначен для вызова Container(Item &&)?

РЕДАКТИРОВАТЬ: Или, другими словами, почему конструктор Container(Item) действителен для такого вызова, как Container c(std::move(i))?

2
Dan 13 Мар 2018 в 15:32

2 ответа

Лучший ответ

Почему конструктор Container (Item) действителен для вызова типа Container c (std :: move (i))?

Потому что вы можете построить Item, получив результат std::move(i) (который является Item&&).

И, как сказал Ховард Хиннант в комментарии - это то же самое для Item vs Item& - вы можете построить один, передав другой, поэтому, если у вас определены оба этих конструктора, вы создали двоякое выражение.

1
einpoklum 13 Мар 2018 в 13:47

Насколько я могу судить, это потому, что аргумент сначала оценивается, а затем передается. Как только он будет принят, это будет любой r-value, и оба ваших конструктора будут работать. Я не уверен, что возможна такая перегрузка, и с моей точки зрения это не имеет смысла.

Почему нет? Что ж, вы пытаетесь определить контейнер, который заставляет элемент перемещаться. С точки зрения дизайна, вы должны учитывать, что кто-то, использующий этот код, может не захотеть «украсть» (переместить) данные. Я думаю, что в этом контексте было бы разумнее иметь:

struct Item {
    Item() { }

    Item(const Item &other) {
        std::cout << "Item::Item(const Item& )\n";
    }
    Item(Item &&other) {
        std::cout << "Item::Item(Item&&)\n";
    }
};

Эта перегрузка имеет больше смысла - вы явно укажете, когда вы хотите переместить объект, а когда скопировать (здесь есть значение по умолчанию move, но это делает его более очевидным). Теперь тебе просто нужно

Container(Item i);

(без другого конструктора) и вызывая его с помощью

Container c(std::move(i));

Это то, что мы ожидаем - вместо копирования i вы явно заявляете, что хотите его переместить, и i отвечает за это.

2
kabanus 13 Мар 2018 в 12:52