Страницы

Поиск по вопросам

понедельник, 6 января 2020 г.

lvalue refference vs rvale refference - примеры на c11++

#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.

Комментариев нет:

Отправить комментарий