2015-04-12 3 views
2

У меня есть проблемы с пониманием пару моментов, касающихся идеальный вариант переадресации как предложено Herb Sutter в своем выступлении "Back to the Basics! Essentials of Modern C++ Style" (@ 1: 15: 00) в CppCon 2014. Три соответствующих слайдов (here are the slides online) являются следующие: enter image description here enter image description here enter image description hereГерба Саттера CppCon Совершенная Forwarding скользит

Я думаю, что в варианте # 4 шаблонного функция член должен быть включен, если гнилой тип String такой же, как std::string и не отличаются, как указано на слайде (в противном случае Варианты № 2 и № 4 не будут эквивалент и не существует std::string оператор присваивания, который в любом случае принимает значение std::string rvalue). Но кроме этого, я не undestand

  • что еще вариант # 4 может украсть кроме rvalues ​​и
  • почему, как показано в тесте на третьем слайде, вариант # 4 будет то, что намного быстрее, чем # 2 (особенно в последнем эталоне). Разве они не должны делать то же самое?
+0

'std :: string' имеет оператор присваивания, который принимает' char const * '. Этот оператор не должен выделять память, если строка 'std :: string' на LHS уже имеет достаточное пространство. – dyp

+0

@ dyp, который применяется также к копированию из 'std :: string'. Кроме того, # 4 не может принимать строковые литералы. – bmanga

+2

Все остальные варианты (но идеальная пересылка) требуют, чтобы вызывающий абонент преобразовывал в 'std :: string' из-за типа параметра. Если аргументом является 'char const *', для этого требуется выделение памяти, даже если созданная 'std :: string' (параметр функции) может быть скопирована без дополнительных распределений на' name_'. Что касается опции №4, то в скобках есть некоторые ошибки. Прекрасная функция пересылки будет использовать 'std :: is_convertible '. – dyp

ответ

2

«Это не то же самое, что» - это шаблон, который вы используете при написании конструкторов, которые идеально конвертируют - вы не хотите использовать этот конвертер, когда переданный тип является некоторым вариантом вашего собственного типа. Скорее всего, он был включен здесь копией-макаронами.

Действительно, вы хотите использовать признак «это может быть присвоено строке»: std::enable_if_t<std::is_assignable<std::string, String>::value>>, так как это то, о чем вы заботитесь. Вы можете пойти дальше и проверить, назначено ли это (если это так, используйте это), и если это будет конвертируемо (и если это так, конвертировать, а затем назначить), но я бы этого не сделал.

Короче говоря, состояние выглядит так, как будто оно происходит из копий-макарон соответствующего теста. Вы действительно не хотите сильно его ограничивать.

Что касается того, почему он опровергает вариант №2, если std::string в вашем контейнере уже выделил память, он может скопировать из char const*, не выделяя больше. Если вместо этого вы берете string&&, то char const* сначала преобразуется в string, то это назначается переносом. У нас есть две строки, и одна из них отбрасывается.

Что вы видите, накладные расходы памяти.

Совершенная пересылка не должна выделять память.


Теперь, в интересах полного, существует еще один вариант. Это немного сумасшествие для реализации, но оно почти так же эффективно, как и вариант №4, и имеет несколько недостатков.

Вариант 5: назначение стирания типа. assignment_view<std::string>.

Напишите класс, который стирает «присвоение типа T». Возьмите его как свой аргумент. Используйте его внутри.

Это более обучаемая, чем идеальная пересылка. Метод может быть виртуальным, поскольку мы берем конкретный тип (конкретный тип назначения строки). Стирание типа происходит во время построения цедента. Некоторый код генерируется для каждого назначенного типа, но код ограничивается только назначением, а не всем телом функции.

Есть некоторые накладные расходы (похожие на вызов виртуальной функции, в основном из-за сбоев кэша команд) при каждом присваивании. Так что это не идеально.

Вы звоните a.assign_to(name) для выполнения задания вместо name = a для максимальной эффективности. Вы можете сделать name << std::move(a);, если вы предпочитаете синтаксис.

Для максимальной эффективности представление стирания назначения (то, что вы хотите назвать) может использоваться только для создания одного задания: это позволяет оптимизировать семантику перемещения. Вы также можете сделать умный, который делает что-то другое на && и & на основе назначения-из-за стоимости одной дополнительной служебной информации указателя функции.

here I type стереть концепцию T == ?. Это просто требует типа, стирающего понятие T = ?. (Я могу сделать синтаксис инициализации {} чуть лучше с Ts&&... CTOR к объекту типа стирания теперь:. Это была моей первой попыткой в ​​этом)

live example типа стирает вплоть до присвоения std::string.

template<class...>struct voider{using type=void;}; 
template<class...Ts>using void_t=typename voider<Ts...>::type; 
template<class T>struct tag{using type=T;}; 

template<class...>struct types{using type=types;}; 

template<class T> 
using block_deduction = typename tag<T>::type; 

template<class F, class Sig, class T=void> 
struct erase_view_op; 

template<class F, class R, class...Ts, class T> 
struct erase_view_op<F, R(Ts...), T> 
{ 
    using fptr = R(*)(void const*, Ts&&...); 

    fptr f; 
    void const* ptr; 

private: 
    template<class U> 
    erase_view_op(U&& u, int): 
    f([](void const* p, Ts&&...ts)->R{ 
     U& u = reinterpret_cast<U&>(*static_cast<std::decay_t<U>*>(const_cast<void*>(p))); 
     return F{}(u, std::forward<Ts>(ts)...); 
    }), 
    ptr(static_cast<void const*>(std::addressof(u))) 
    {} 
public: 
    template<class U, class=std::enable_if_t< !std::is_same<std::decay_t<U>,erase_view_op>{} && (std::is_same<void,R>{} || std::is_convertible< std::result_of_t<F(U,Ts...)>, R >{}) >> 
    erase_view_op(U&& u):erase_view_op(std::forward<U>(u), 0){} 

    template<class U=T, class=std::enable_if_t< !std::is_same<U, void>{} >> 
    erase_view_op(block_deduction<U>&& u):erase_view_op(std::move(u), 0){} 

    erase_view_op(erase_view_op const&) = default; 
    erase_view_op(erase_view_op&&) = default; 

    R operator()(Ts... ts) const { 
    return f(ptr, std::forward<Ts>(ts)...); 
    } 
}; 

struct assign_lhs_to_rhs { 
    template<class lhs, class rhs> 
    void operator()(lhs&& l, rhs& r)const { 
    r = std::forward<lhs>(l); 
    } 
}; 
template<class T> 
using erase_assignment_to = erase_view_op< assign_lhs_to_rhs, void(T&), T >; 
using string_assign_to = erase_assignment_to<std::string>; 

это, как уже отмечалось, весьма похож на тип стирания вплоть до ==. Я сделал некоторые скромные улучшения (void тип возврата). Идеальная пересылка (до T{}) ctor была бы лучше, чем block_deduction<U>&& одна (как вы получите {} вместо {{}}).

+0

@ dyp мое лицо красное. Удаление неправильного предложения. – Yakk

+0

Ну, я тоже совсем забыл об этой черту, пока Говард Хиннант не упомянул об этом;) - хотя я не совсем уверен, что это подходящая черта здесь. 'is_convertible' имитирует ограничения функции с параметром' std :: string const & '. Как T.C. отметил, вы можете присвоить 'double'' string', но вы не можете преобразовать 'double' в' string'. – dyp

+0

@dyp Я думаю, что это ошибка в 'string' больше всего на свете. Во всяком случае, добавлена ​​реализация стирания типа. Теперь вы можете использовать 'erase_assignment_to ' или 'string_assign_to' как статический параметр. Довольно глупо, если ваш код является одной строкой, но может быть проще в использовании, чем ручная совершенная переадресация для нового пользователя. – Yakk

4

Там есть ошибка в слайде, она должна быть std::enable_if<std::is_same<..., а на самом деле, не было никакой ошибки в слайдах, которые фактически были показаны во время разговора, вы можете увидеть его at 1:16:58:

enter image description here

И да, как указывал @dyp, std::enable_if_t<std::is_convertible<String, std::string>::value>> имеет гораздо больше смысла.

+1

Если функция включена, если аргумент функции является строкой 'std :: string', вы не получите преимуществ для аргументов' char const * '. (Фактически, вы не сможете передавать 'char const *' без дополнительных перегрузок или ручного преобразования.) – dyp

+2

@dyp: технику можно настроить немного лучше с помощью 'std :: is_assignable '. Общее руководство для автора - посмотреть, что делает ваша реализация, и использовать характеристики, которые лучше всего описывают вашу реализацию для ограничений вашего шаблона. –

+0

Это то, что я тоже подумал, но поскольку dyp упоминается с использованием 'std :: is_convertible ', вместо этого лучше ответить на мои вопросы. – bmanga

Смежные вопросы