Домівка > C++ > Досконала переадресація

Досконала переадресація

Загальна форма проблеми переадресації має такий вигляд:

Для заданого виразу E(a1, a2, ..., an), що залежить від (шаблонних) параметрів a1, a2, ..., an, написати функцію (об’єкт) f такий, що виклик f(a1, a2, ..., an) тотожний виклику E(a1, a2, ..., an).

Стандарт C++11 уможливив створення таких функцій (об’єктів) завдяки rvalue посиланням.

Розглянемо просту функцію-фабрику:

template<typename T, typename Arg> 
shared_ptr<T> factory(Arg arg)
{ 
  return shared_ptr<T>(new T(arg));
}

Очевидно, що намір тут був переадресувати аргумент arg з функції-фабрики у конструктор T. Ідеально було б, щоб все відбулось так, наче функція-фабрика тут відсутня і конструктор викликали прямо з клієнтського коду: досконала переадресація (perfect forwarding). Наведений вище код зазнає жахливої невдачі: він вводить додатковий виклик по значенню, що особливо погано, якщо конструктор приймає аргумент як посилання, отже зміни не будуть видні зовні функції-фабрики.

Найпоширеніший розв’язок – дозволити зовнішній функції приймати аргумент як посилання:

template<typename T, typename Arg> 
shared_ptr<T> factory(Arg& arg)
{ 
  return shared_ptr<T>(new T(arg));
}

Це ліпше, але не досконало. проблема в тому, що тепер функцію не можна викликати на rvalue:

factory<X>(hoo()); // помилка якщо hoo повертає по значенню
factory<X>(41); // помилка

Це можна виправити якщо надати перевантажену версію, яка приймає стале посилання:

template<typename T, typename Arg> 
shared_ptr<T> factory( Arg const & arg)
{ 
  return shared_ptr<T>(new T(arg));
}

У цього підходу декілька проблем. По-перше, якщо у фабрики декілька параметрів, необхідно було б надати перевантаження для всіх можливих комбінацій сталих і несталих посилань. Отже, рішення масштабується дуже погано для функцій з кількома аргументами.

По-друге, такий тип переадресації не досконалий, оскільки він відбраковує семантику переносу: аргумент конструктора T у тілі функції-фабрики є lvalue. Отже, семантика переносу ніколи не спрацює, навіть якщо вона б спрацювали без функції-обгортки.

Виявляється, що можна використати rvalue для того, щоб розв’язати обидві ці проблеми. Щоб зрозуміти як, нам потрібно розглянути два правила щодо rvalue посилань.

Перше правило зачіпає lvakue посилання також. До появи C++11 було заборонено брати посилання на посилання: щось на кшталт A& & викликало б помилку компіляції. C++11 впроваджує такі правила скорочення посилань:

  • A& & стає A&
  • A& && стає A&
  • A&& & стає A&
  • A&& && стає A&&
  • І по-друге, існує спеціальне правило виведення шаблонних аргументів, що беруть аргумент як rvalue посилання:

    template<typename T>
    void foo(T&&);
    

    Тут застосовується наступне:

    1. Коли foo викликається на lvalue типу A, тоді T стає A& і, отже, завдяки правилам скорочення посилань наведеним вище, тип аргументу становиться A&.
    2. Коли foo викликається на rvalue типу A, тоді T стає A і, отже, тип аргументу стає A&&.

    Озброєні цими правилами, ми можемо використати rvalue для розв’язання проблеми досконалого перенаправлення. Ось як виглядає розв’язок:

    template<typename T, typename Arg> 
    shared_ptr<T> factory(Arg&& arg)
    { 
      return shared_ptr<T>(new T(std::forward<Arg>(arg)));
    }
    

    де сирцевий код std::forward такий:

    template<class S>
    S&& forward(typename remove_reference<S>::type& a) noexcept
    {
      return static_cast<S&&>(a);
    }
    

    Щоб побачити як цей код працює, ми прослідкуємо що відбувається коли функція-фабрика викликається на lvalue та rvalue. Припустімо, що factory<A> викликали з lvalue типу X:

    X x;
    factory<A>(x);
    

    Тоді, згідно з особливими правилами виведення шаблонів, вказаними вище, шаблонний аргумент фабрики Arg стає X&. Отже, компілятор створить такі примірники factory і std::forward:

    shared_ptr<A> factory(X& && arg)
    { 
      return shared_ptr<A>(new A(std::forward<X&>(arg)));
    } 
    
    X& && forward(remove_reference<X&>::type& a) noexcept
    {
      return static_cast<X& &&>(a);
    }
    

    Після виконання remove_reference і застосування правил скорочення посилань, маємо:

    shared_ptr<A> factory(X& arg)
    { 
      return shared_ptr<A>(new A(std::forward<X&>(arg)));
    } 
    
    X& std::forward(X& a) 
    {
      return static_cast<X&>(a);
    }
    

    Це вже дійсно досконале перенаправлення для lvalu: аргумент фабрики arg потрапляє до конструктора A через два рівні переадресації, обидва рази через звичайні lvalue посилання.

    Тепер уявімо, що factory<A> викликається з rvalue типу `X:’

    X foo();
    factory<A>(foo());
    

    Тут знов, завдяки особливим правилам виведення аргументів шаблону заявленим вище, шаблонний аргумент фабрики Arg стає X. Отже, компілятор створить такі примірники шаблонів:

    shared_ptr<A> factory(X&& arg)
    { 
      return shared_ptr<A>(new A(std::forward<X>(arg)));
    } 
    
    X&& forward(X& a) noexcept
    {
      return static_cast<X&&>(a);
    }
    

    Це й насправді досконале перенаправлення для rvalue: аргумент функції-фабрики передали у конструктор A через два рівні переадресації, обидва як посилання. Більше того, конструктор A бачить аргумент який визначено як rvalue і який не має імені. Відповідно до наступного правила такий аргумент є rvalue. Отже конструктор A отримує rvalue. Це означає, що переадресація зберігає можливу семантику переносу яка б сталась якщо функція-фабрика була б відсутня.

    Щось визначене як rvalue посилання може бути як rvalue так і lvalue. Критерієм вирізняння є: якщо воно має ім’я, то це lvalue. Інакше, це rvalue.

    Advertisements
    Категорії:C++ Позначки:, ,
    1. Коментарів ще немає.
    1. No trackbacks yet.

    Залишити відповідь

    Заповніть поля нижче або авторизуйтесь клікнувши по іконці

    Лого WordPress.com

    Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

    Twitter picture

    Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

    Facebook photo

    Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

    Google+ photo

    Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

    З’єднання з %s

    %d блогерам подобається це: