언제 std :: forward를 사용하여 인수를 전달합니까?


155

C ++ 0x는 다음을 사용하는 예를 보여줍니다 std::forward.

template<class T>
void foo(T&& arg) 
{
  bar(std::forward<T>(arg));
}

언제 사용하는 것이 유리 std::forward합니까?

또한 &&매개 변수 선언 에 사용해야 합니다. 모든 경우에 유효합니까? 함수가 선언 된 경우 임시 함수를 함수에 전달해야한다고 생각 &&했으므로 매개 변수로 foo를 호출 할 수 있습니까?

마지막으로 다음과 같은 함수 호출이있는 경우 :

template<int val, typename... Params>
void doSomething(Params... args) {
  doSomethingElse<val, Params...>(args...);
}

대신 이것을 사용해야합니까?

template<int val, typename... Params>
void doSomething(Params&&... args) {
  doSomethingElse<val, Params...>(std::forward<Params>(args)...);
}

또한 함수에서 매개 변수를 두 번 사용하는 경우 (즉, 동시에 두 개의 함수로 전달) 사용하는 것이 std::forward좋습니까? std::forward같은 것을 일시적으로 두 번 변환 하지 않고 메모리를 이동하고 두 번째 사용에는 유효하지 않습니까? 다음 코드가 정상입니까?

template<int val, typename... Params>
void doSomething(Params&&... args) {
  doSomethingElse<val, Params...>(std::forward<Params>(args)...);
  doSomethingWeird<val, Params...>(std::forward<Params>(args)...);
}

나는 약간 혼란스럽고 std::forward기꺼이 정리를 사용합니다.

답변:


124

첫 번째 예제처럼 사용하십시오.

template <typename T> void f(T && x)
{
  g(std::forward<T>(x));
}

template <typename ...Args> void f(Args && ...args)
{
  g(std::forward<Args>(args)...);
}

그 때문에의의 규칙을 무너 참조 : 만약 T = U&, 다음 T&& = U&,하지만 만약 T = U&&다음 T&& = U&&, 당신은 항상 함수 본문 내부의 올바른 유형으로 끝낼 수 있도록. 마지막으로 forwardlvalue-turned x(지금 이름이 있기 때문에)를 rvalue 참조로 되돌려 야합니다.

즉 일반적으로 의미하지 않기 때문에 당신은, 그러나 한 번 이상 전달하지 뭔가를해야한다 : 당신은 가능성이 있다는 것을 전달 수단 움직이는 모든 방법을 최종 호출을 통해 인수를하고 이동 된 후에는 당신이 그것을 사용하지 수 있도록이 사라 졌어요 다시 (당신이 아마 의도했던 방식으로).


나는 생각했다 Args...&& args?
강아지

5
@DeadMG : 그것은 내가 잘못 기억하는 것이 아니라 항상 올바른 것입니다 :-) ...이 경우에는 올바르게 잘못 기억 한 것 같습니다!
Kerrek SB

1
그러나 일반 유형 T에 대해 g는 어떻게 선언됩니까?
MK.

@MK. g는 원하는 매개 변수를 사용하여 일반 함수로 선언됩니다.
CoffeDeveloper

1
@cmdLP : 반복적으로 전달하는 것이 잘 정의되어 있지만, 의미 상 프로그램에 대한 의미는 거의 없습니다. 그러나 앞으로 표현을 만드는 것은 유용한 경우입니다. 답변을 업데이트하겠습니다.
Kerrek SB

4

Kerrek의 답변은 매우 유용하지만 제목의 질문에 완전히 대답하지는 않습니다.

언제 std :: forward를 사용하여 인수를 전달합니까?

이에 대한 답을 얻기 위해서는 먼저 보편적 참조 개념을 도입해야합니다 . Scott Meyers는이 이름을 지정했으며 오늘날에는 종종 전달 참조라고합니다. 기본적으로 다음과 같은 것을 볼 때 :

template<typename T>
void f(T&& param);

paramrvalue 참조가 아니라 결론을 내릴 수도 있음 을 명심하십시오 . 범용 참조는 매우 제한된 형식 ( T&&const 또는 유사한 한정자가없는)과 유형 추론에 의해 특징 지어집니다 . 유형 이 호출 T될 때 유형 이 추론 f됩니다. 간단히 말해 범용 참조는 rvalue로 초기화 된 경우 rvalue 참조에 해당하고 lvalue로 초기화 된 경우 lvalue 참조에 해당합니다.

이제 원래 질문에 대답하기가 비교적 쉽습니다 std::forward.

  • 함수에서 마지막으로 사용 된 범용 참조
  • 값으로 반환되는 함수에서 반환되는 범용 참조

첫 번째 사례의 예 :

template<typename T>
void foo(T&& prop) {
    other.set(prop); // use prop, but don't modify it because we still need it
    bar(std::forward<T>(prop)); // final use -> std::forward
}

위의 코드에서 완료 prop후 알 수없는 값을 원하지 other.set(..)않기 때문에 전달이 발생하지 않습니다. 그러나, 우리가 끝났을 때 bar앞으로 전화 를 걸면 원하는대로 무엇이든 할 수 있습니다 (예 : 이동).propbar

두 번째 경우의 예 :

template<typename T>
Widget transform(T&& prop) {
   prop.transform();
   return std::forward<T>(prop);
}

이 함수 템플릿은 proprvalue 인 경우 반환 값으로 이동 하고 lvalue 인 경우 복사해야합니다. std::forward마지막에 생략 한 경우에는 항상 사본을 작성하는데, 이는 proprvalue 일 때 더 비쌉니다 .

* 완전히 참조하기 위해 범용 참조는 cv-unqualified 템플릿 매개 변수에 대한 rvalue 참조를 취하는 개념입니다.


0

이 예가 도움이됩니까? 나는 std :: forward의 유용한 비 일반적인 예를 찾기 위해 고심했지만, 우리가 인수로 입금하기 위해 현금을 통과하는 은행 계좌의 예에 부딪쳤다.

만약 우리가 계정의 const 버전을 가지고 있다면 우리가 예금 템플릿 <>에 전달할 때 const 함수가 호출 될 것으로 예상해야합니다. 그런 다음 예외가 발생합니다 (이 아이디어는 잠긴 계정이었습니다!)

const 계정이 아닌 계정이 있으면 계정을 수정할 수 있어야합니다.

#include <iostream>
#include <string>
#include <sstream> // std::stringstream
#include <algorithm> // std::move
#include <utility>
#include <iostream>
#include <functional>

template<class T> class BankAccount {
private:
    const T no_cash {};
    T cash {};
public:
    BankAccount<T> () {
        std::cout << "default constructor " << to_string() << std::endl;
    }
    BankAccount<T> (T cash) : cash (cash) {
        std::cout << "new cash " << to_string() << std::endl;
    }
    BankAccount<T> (const BankAccount& o) {
        std::cout << "copy cash constructor called for " << o.to_string() << std::endl;
        cash = o.cash;
        std::cout << "copy cash constructor result is  " << to_string() << std::endl;
    }
    // Transfer of funds?
    BankAccount<T> (BankAccount<T>&& o) {
        std::cout << "move cash called for " << o.to_string() << std::endl;
        cash = o.cash;
        o.cash = no_cash;
        std::cout << "move cash result is  " << to_string() << std::endl;
    }
    ~BankAccount<T> () {
        std::cout << "delete account " << to_string() << std::endl;
    }
    void deposit (const T& deposit) {
        cash += deposit;
        std::cout << "deposit cash called " << to_string() << std::endl;
    }
    friend int deposit (int cash, const BankAccount<int> &&account) {
        throw std::string("tried to write to a locked (const) account");
    }
    friend int deposit (int cash, const BankAccount<int> &account) {
        throw std::string("tried to write to a locked (const) account");
    }
    friend int deposit (int cash, BankAccount<int> &account) {
        account.deposit(cash);
        return account.cash;
    }
    friend std::ostream& operator<<(std::ostream &os, const BankAccount<T>& o) {
        os << "$" << std::to_string(o.cash);
        return os;
    }
    std::string to_string (void) const {
        auto address = static_cast<const void*>(this);
        std::stringstream ss;
        ss << address;
        return "BankAccount(" + ss.str() + ", cash $" + std::to_string(cash) + ")";
    }
};

template<typename T, typename Account>
int process_deposit(T cash, Account&& b) {
    return deposit(cash, std::forward<Account>(b));
}

int main(int, char**)
{
    try {
        // create account1 and try to deposit into it
        auto account1 = BankAccount<int>(0);
        process_deposit<int>(100, account1);
        std::cout << account1.to_string() << std::endl;
        std::cout << "SUCCESS: account1 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account1 deposit failed!: " << e << std::endl;
    }

    try {
        // create locked account2 and try to deposit into it; this should fail
        const auto account2 = BankAccount<int>(0);
        process_deposit<int>(100, account2);
        std::cout << account2.to_string() << std::endl;
        std::cout << "SUCCESS: account2 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account2 deposit failed!: " << e << std::endl;
    }

    try {
        // create locked account3 and try to deposit into it; this should fail
        auto account3 = BankAccount<int>(0);
        process_deposit<int>(100, std::move(account3));
        std::cout << account3.to_string() << std::endl;
        std::cout << "SUCCESS: account3 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account3 deposit failed!: " << e << std::endl;
    }
}

짓다:

cd std_forward
rm -f *.o example
c++ -std=c++2a -Werror -g -ggdb3 -Wall -c -o main.o main.cpp
c++ main.o  -o example
./example

예상 출력 :

# create account1 and try to deposit into it
new cash BankAccount(0x7ffee68d96b0, cash $0)
deposit cash called BankAccount(0x7ffee68d96b0, cash $100)
BankAccount(0x7ffee68d96b0, cash $100)
# SUCCESS: account1 deposit succeeded!
delete account BankAccount(0x7ffee68d96b0, cash $100)

# create locked account2 and try to deposit into it; this should fail
new cash BankAccount(0x7ffee68d9670, cash $0)
delete account BankAccount(0x7ffee68d9670, cash $0)
# FAILED: account2 deposit failed!: tried to write to a locked (const) account

# create locked account3 and try to deposit into it; this should fail
new cash BankAccount(0x7ffee68d9630, cash $0)
delete account BankAccount(0x7ffee68d9630, cash $0)
# FAILED: account3 deposit failed!: tried to write to a locked (const) account
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.