Wywołanie konstruktora przenoszącego

0

Witam !
Mam taki kod

class Temp {
public:
  Temp(int a): a_{a} {std::cout << "Temp" << std::endl;}
  Temp(const Temp& t): a_{t.a_} {std::cout << "copy Temp" << std::endl;}
  Temp(Temp&& t): a_{t.a_} {std::cout << "move Temp" << std::endl;}
  auto show() {
    return a_;
  }
private:
 int a_{};
};

template <typename T>
void func(T t) {
  std::cout << t.show() << std::endl;
}

int main() {
  Temp t{100};
  func(t);
  func(std::move(Temp{3}));
}

output to:

Temp
copy Temp
100
Temp
move Temp
3

Tak jak się spodziewałem. Tylko teraz jak zamienie typ argumentu w void func(T t) na void func(T&& t) to output mam

Temp 
100
Temp
3

Dlaczego nie ma tutaj wywołania konstruktora przenoszącego move Temp ?

3

Ponieważ T t jest obiektem. Żeby stworzyć obiekt musisz wywołać jakiś konstruktor. Stąd copy Temp i move Temp.
Za to T&& t to referencja - nowy obiekt nie jest tworzony, więc nie jest wołany konstruktor.

0

To T&& oznacza referencję do l-value i r-value ? Czym się rózni T&& od const T& ?

Jak zmieniłem typ argumentu na const T& to program trzeba skompilować z flagą -fpermissive bo wywala
error: passing ‘const Temp’ as ‘this’ argument discards qualifiers [-fpermissive] 18 | std::cout << t.show() << std::endl;

2

Jeśli chcesz użyć const T & to nie musisz kompilować z -fpermissive tylko możesz naprawić kod.
Problem jest tutaj:

auto show() {

Metody show nie da się wywołać na stałym obiekcie (metoda nie jest const).

Jeśli zrobisz tak:

auto show() const {

To kod się kompiluje.

0

dziękuje za pomoc !

1

To T&& oznacza referencję do l-value i r-value ? Czym się rózni T&& od const T& ?

T&& to r-value reference.

Różni to się tym, że jak masz:

void func(T &&t) {

to t możesz przenieść do innego obiektu.

A jak masz:

void func(const T &t) {

to nie możesz.

Dodatkowo, przy T &&t jak chcesz wywołać func to musisz dysponować obiektem który nie jest const.

Edit:
Chociaż, niezupełnie. Jakoś zignorowałem, że to jest szablon. template< typename T > w połączeniu z T && to perfect forwarding. Czyli taki typ jaki podasz przy wołaniu funkcji, zostanie dostarczony do funkcji.

0

A co się dzieje jak mamy typ argumentu funkcji T&& a przekażemy do funkcji l-value? Wtedy funkcja to odczyta jak referencję do l-value (T& ) ?

1
ProgrammingKing napisał(a):

A co się dzieje jak mamy typ argumentu funkcji T&& a przekażemy do funkcji l-value? Wtedy funkcja to odczyta jak referencję do l-value (T& ) ?

To będzie l-value.

Do zrozumienia perfect forwarding zrób sobie coś takiego:

template <typename T>
void func(T &&t) {
  if( std::is_same_v< decltype( t ), Temp > ) std::cout << "type: Temp" << std::endl;
  if( std::is_same_v< decltype( t ), Temp & > ) std::cout << "type: Temp &" << std::endl;
  if( std::is_same_v< decltype( t ), const Temp & > ) std::cout << "type: const Temp &" << std::endl;
  if( std::is_same_v< decltype( t ), Temp && > ) std::cout << "type: Temp &&" << std::endl;
  if( std::is_same_v< decltype( t ), const Temp && > ) std::cout << "type: const Temp &&" << std::endl;
}

I wywołaj tak jak wołasz:

int main() {
  Temp t{100};

  func(t);
  func(std::move(Temp{3}));

  const Temp &cr = t;
  func( cr );

  Temp &r = t;
  func( r );

  const Temp &&rr = std::move( t );
  func( std::move( rr ) );
}
Temp
type: Temp &
Temp
type: Temp &&
type: const Temp &
type: Temp &
type: const Temp &&

https://godbolt.org/z/7Yqova1dh

1

Naprawdę nikomu nie podnosi się alarm jak ktoś pisze std::move(Temp{3})?
Temp{3} to r-value więc std::move jeszcze pogarsza sprawę, co kompilator wyłapuje (jak korzysta się z ostrzeżeń): zmodyfikowana wersja:

#include <iostream>
#include <utility>

#define LOG(X) std::cout << __PRETTY_FUNCTION__ << ' ' \
    << #X "=" << (X) << std::endl

class Temp {
public:
    Temp(int a)
        : a_ { a }
    {
        LOG(a);
    }
    Temp(const Temp& t)
        : a_ { t.a_ }
    {
        LOG(a_);
    }
    Temp(Temp&& t)
        : a_ { t.a_ }
    {
        LOG(a_);
    }
    auto show()
    {
        return a_;
    }

private:
    int a_ {};
};

template <typename T>
void func(T t)
{
    LOG(t.show());
}

int main()
{
    Temp t { 100 };
    func(t);
    func(std::move(Temp { 3 }));
}

Co produkuje ostrzeżenie:

<source>: In function 'int main()':
<source>:43:19: warning: moving a temporary object prevents copy elision [-Wpessimizing-move]
   43 |     func(std::move(Temp { 3 }));
      |          ~~~~~~~~~^~~~~~~~~~~~
<source>:43:19: note: remove 'std::move' call

Bez move jest lepiej https://godbolt.org/z/MbzG3vYn6
Ale jeszcze lepiej skorzystać z perfect forwarding: https://godbolt.org/z/oW3Eh5dx1

Polecam doczytać jakaaś dobrą książkę o perfect forwarding (np coś od Mayersa). To jest dość zawiły temat, gdzie trudno coś dobrze wytłumaczyć w krótkiej wypowiedzi internetowej.

0
ProgrammingKing napisał(a):

To T&& oznacza referencję do l-value i r-value ? Czym się rózni T&& od const T& ?

Dokładna odpowiedź to: "to zależy". W przypadku tego template'a to będzie universal reference (zarówno l-value jak i r-value w zależności od tego, z jakim argumentem wywołasz funkcję).
Polecam lekturę tego artykułu: https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

1 użytkowników online, w tym zalogowanych: 0, gości: 1