#cpp11 #function_overloading
// Example program #include#include void f(const int& x) { std::cout << "lvalue reference to const overload f(" << x << ")\n"; } void f(int&& x) { std::cout << "rvalue reference overload f(" << x << ")\n"; } #include #include int main() { f(3); int&& z = 3; f(z); } Почему в данном случае при вызове f(z), вызывается const lvalue функция, а не вторая, ведь тип z является rvalue refference ? и второй вопрос: class A { public: A() { std::cout << "A constructor" << std::endl; } ~A() { std::cout << "A destructor" << std::endl; } A(const A& other) { std::cout << "A copy constructor" << std::endl; } A(A&& other) { std::cout << "A move constructor" << std::endl; } }; class B : public A { public: B() : A() { std::cout << "B constructor" << std::endl; } ~B() { std::cout << "B destructor" << std::endl; } B(const B& other) : A(other) { std::cout << "B copy constructor" << std::endl; } B(B&& other) : A(other) { std::cout << "B move constructor" << std::endl; } }; int main() { std::vector v; std::cout << ">>> Pushing first element" << std::endl; v.push_back(B()); std::cout << ">>> First element was pushed" << std::endl; } Почему в данном случае вызывается A copy constructor, а не A move constructor: как это связанно с lvalue и rvalue, и с overloading ? update: изменил строчку как и было сказано в отличном ответе: B(B&& other) : A(std::move(other)) { Благодарю чему вывод теперь выглядит так: >>> Pushing first element A constructor B constructor A move constructor B move constructor B destructor A destructor >>> First element was pushed >>> Pushing second element A constructor B constructor A move constructor B move constructor A copy constructor B copy constructor B destructor A destructor B destructor A destructor >>> Second element was pushed B destructor A destructor B destructor A destructor Обновленный вопрос: Последние два copy ctor'a вызываются насколько я понимаю при увеличение величины вектора и при их копирование (насколько я понимаю, реализация через динамический массив), если не в этом причина, поправьте. Как сделать так, чтобы и тех местах вызывался move ctor ? попробовал так: v.push_back(std::move)B()(); Ничего не изменило. Когда эти элементы уже находятся в векторе, насколько я понимаю, они уже являются lvalue. Странность в том, что в задание (исследование поведение rvalue и lvalue) сказано, что можно дополнительно изменить как-то ту же строчку о которой была речь выше каким-то специальным specifier'ом, который ради таких случаев добавлен в c++11, чтобы вызывать именно move constructor и в этом случае. Подскажите как это сделать?
Ответы
Ответ 1
Именованные rvalue-ссылки рассматриваются как lvalue. В этом объявлении int&& z = 3; переменная z имеет тип rvalue-ссылки, которая привязана к временному выражению, состоящего из целочисленного литерала, равного 3. Тем не менее, переменная z - это lvalue. Например, вы можете применить оператор получения адреса std::cout << ( void * )&z << std::endl; Об этом, например, явно сказано в параграфе №7 раздела 5 Expressions стандарта C++ In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether named or not. Поэтому когда вызывается функция f, то компилятор ищет перегруженную функцию, которая принимает в качестве аргумента lvalue. В результате будет выбрана функция, объявленная как void f(const int& x) { std::cout << "lvalue reference to const overload f(" << x << ")\n"; } Вы можете превратить z в rvalue , используя приведение типв static_cast, или стандартную функцию std::move, или просто для арифметического типа, например, поставив унарный знак плюса. Ниже приведена демонстрационная программа, которая показывает описанные подходы по превращению lvalue в rvalue, и что переменная z, хотя она имеет тип rvalue-ссылки, тем не менее является lvalue. #include#include void f(int&& x) { std::cout << "rvalue reference overload f(" << x << ")\n"; } void f(const int& x) { std::cout << "lvalue reference to const overload f(" << x << ")\n"; } int main() { f( 3 ); int &&z = 3; f( z ); f( static_cast ( z ) ); f( std::move( z ) ); f( +z ); return 0; } Вывод программы на консоль rvalue reference overload f(3) lvalue reference to const overload f(3) rvalue reference overload f(3) rvalue reference overload f(3) rvalue reference overload f(3) Аналогичная ситуация имеет место и с параметром конструктора. Хотя он имеет тип rvalue-ссылки, тем не менее сам параметр является lvalue. Поэтому когда он передается базовому классу в качестве аргумента, то будет вызываться конструктор базового класса, который принимает lvalue. Если хотите поподробнее познакомиться с этим материалом, то советую почитать статью Скотта Майерса в журнале ACCU Overload №111 за октябрь 2012 года Universal References in C++11. Это международный журнал профессиональных программистов. Кстати сказать, заодно в этом же журнале ACCU Overload #126 за апрель 2015 года можете почитать мою статью iterator_pair - a simple and useful iterator adapter.:) Относительно ACCU, то это The ACCU is an organisation of programmers who care about professionalism in programming. That is, we care about writing good code, and about writing it in a good way. We are dedicated to raising the standard of programming. The articles in this magazine have all been written by ACCU members - by programmers, for programmers - and have been contributed free of charge Что касается вашего дополнительного вопроса, то просто зарезервируйте достаточно место в векторе, как , например, v.reserve( 2 ); Тогда не будет перераспределения памяти, и, соответственно, копирования объектов вектора на новый участок памяти. Либо у конструктора перемещения укажите спецификатор noexcept. Тогда вместо конструктора копирования будет вызываться конструктор перемещения. Вот простой пример #include #include struct B { B() { std::cout << "B()" << std::endl; } B( const B & ) { std::cout << "B( const B & )" << std::endl; } B( B && ) noexcept { std::cout << "B( B && )" << std::endl; } // ^^^^^^^ ~B() { std::cout << "~B()" << std::endl; } }; int main() { std::vector v; std::cout << "adding first element" << std::endl; v.push_back( B() ); std::cout << "\nadding second element" << std::endl; v.emplace_back( B() ); return 0; } Ответ 2
В силу моего разумения - в стандартах я не до такой степени силен, чтоб навскидку цитировать. В первой части - объявляйте z как угодно, но раз есть имя - есть адрес, а значит, это будет попросту int. Во второй - после того как rvalue-ссылка передана как параметр, она получает имя, а значит, теперь это lvalue. Хотите перемещающий конструктор - делайте так: B(B&& other) : A(std::move(other)) { Тот же способ можно применить и к f(z): f(std::move(z)). Конкретные ссылки на стандарт, думаю, даст @Vlad from Moscow.
Комментариев нет:
Отправить комментарий