Как далеко в кроличьей норе вы хотите зайти?
Я знаю 4 достойных способа подойти к этой проблеме. Как правило, вы должны использовать более ранние, если вы соответствуете их предварительным условиям, так как каждый последующий значительно увеличивает сложность.
По большей части, либо перемещение настолько дешево, что дважды это бесплатно, либо перемещение — это копирование.
Если ход копируется, а копия несвободна, берем параметр const&
. Если нет, берите по значению.
Это будет вести себя в основном оптимально и сделает ваш код намного проще для понимания.
LinearClassifier(Loss loss, Optimizer const& optimizer)
: _loss(std::move(loss))
, _optimizer(optimizer)
{}
для дешевого перемещения Loss
и перемещения-копии optimizer
.
Это делает 1 дополнительный шаг по сравнению с «оптимальной» идеальной переадресацией ниже (примечание: идеальная переадресация не оптимальна) для каждого параметра значения во всех случаях. Пока перемещение дешевое, это лучшее решение, потому что оно генерирует четкие сообщения об ошибках, позволяет построение на основе {}
и его намного легче читать, чем любое другое решение.
Рассмотрите возможность использования этого решения.
Если перемещение дешевле, чем копирование, но не является бесплатным, один из подходов идеально основан на пересылке: Либо:
template<class L, class O >
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))
{}
Или более сложный и удобный для перегрузки:
template<class L, class O,
std::enable_if_t<
std::is_same<std::decay_t<L>, Loss>{}
&& std::is_same<std::decay_t<O>, Optimizer>{}
, int> * = nullptr
>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))
{}
это стоит вам возможности строить свои аргументы на основе {}
. Кроме того, приведенный выше код может сгенерировать до экспоненциального числа конструкторов, если они будут вызваны (надеюсь, они будут встроены).
Вы можете отказаться от предложения std::enable_if_t
за счет отказа SFINAE; в основном, неправильная перегрузка вашего конструктора может быть выбрана, если вы не будете осторожны с этим предложением std::enable_if_t
. Если у вас есть перегрузки конструктора с одинаковым количеством аргументов или вы заботитесь о раннем сбое, вам нужен std::enable_if_t
. В противном случае используйте более простой.
Это решение обычно считается "наиболее оптимальным". Это приемлемо оптимально, но не наиболее оптимально.
Следующим шагом будет использование конструкции emplace с кортежами.
private:
template<std::size_t...LIs, std::size_t...OIs, class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::index_sequence<LIs...>, std::tuple<Ls...>&& ls,
std::index_sequence<OIs...>, std::tuple<Os...>&& os
)
: _loss(std::get<LIs>(std::move(ls))...)
, _optimizer(std::get<OIs>(std::move(os))...)
{}
public:
template<class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
std::tuple<Ls...> ls,
std::tuple<Os...> os
):
LinearClassifier(std::piecewise_construct_t{},
std::index_sequence_for<Ls...>{}, std::move(ls),
std::index_sequence_for<Os...>{}, std::move(os)
)
{}
где мы откладываем строительство до тех пор, пока внутри LinearClassifier
. Это позволяет вам иметь некопируемые/перемещаемые объекты в вашем объекте и, возможно, максимально эффективно.
Чтобы увидеть, как это работает, пример теперь piecewise_construct
работает с std::pair
. Сначала вы передаете кусочную конструкцию, затем forward_as_tuple
аргументы для последующего построения каждого элемента (включая копирование или перемещение ctor).
Создавая объекты напрямую, мы можем исключить перемещение или копирование для каждого объекта по сравнению с приведенным выше решением с идеальной переадресацией. Это также позволяет вам пересылать копию или двигаться, если это необходимо.
Последний симпатичный прием — стирание текста. На практике для этого требуется что-то вроде std::experimental::optional<T>
, что может сделать класс немного больше.
Это не быстрее, чем кусочное построение. Он абстрагирует работу, которую выполняет конструкция emplace, упрощая ее для каждого использования, и позволяет вам отделить тело ctor от файла заголовка. Но есть небольшие накладные расходы, как во время выполнения, так и в пространстве.
Есть куча шаблонов, с которых вам нужно начать. Это создает класс шаблона, который представляет концепцию «создания объекта позже в месте, которое мне скажет кто-то другой».
struct delayed_emplace_t {};
template<class T>
struct delayed_construct {
std::function< void(std::experimental::optional<T>&) > ctor;
delayed_construct(delayed_construct const&)=delete; // class is single-use
delayed_construct(delayed_construct &&)=default;
delayed_construct():
ctor([](auto&op){op.emplace();})
{}
template<class T, class...Ts,
std::enable_if_t<
sizeof...(Ts)!=0
|| !std::is_same<std::decay_t<T>, delayed_construct>{}
,int>* = nullptr
>
delayed_construct(T&&t, Ts&&...ts):
delayed_construct( delayed_emplace_t{}, std::forward<T>(t), std::forward<Ts>(ts)... )
{}
template<class T, class...Ts>
delayed_construct(delayed_emplace_t, T&&t, Ts&&...ts):
ctor([tup = std::forward_as_tuple(std::forward<T>(t), std::forward<Ts>(ts)...)]( auto& op ) mutable {
ctor_helper(op, std::make_index_sequence<sizeof...(Ts)+1>{}, std::move(tup));
})
template<std::size_t...Is, class...Ts>
static void ctor_helper(std::experimental::optional<T>& op, std::index_sequence<Is...>, std::tuple<Ts...>&& tup) {
op.emplace( std::get<Is>(std::move(tup))... );
}
void operator()(std::experimental::optional<T>& target) {
ctor(target);
ctor = {};
}
explicit operator bool() const { return !!ctor; }
};
где мы стираем действие создания необязательных параметров из произвольных аргументов.
LinearClassifier( delayed_construct<Loss> loss, delayed_construct<Optimizer> optimizer ) {
loss(_loss);
optimizer(_optimizer);
}
где _loss
это std::experimental::optional<Loss>
. Чтобы удалить необязательность _loss
, вы должны использовать std::aligned_storage_t<sizeof(Loss), alignof(Loss)>
и быть очень осторожным при написании ctor для обработки исключений и ручного уничтожения вещей и т. д. Это головная боль.
Некоторые приятные особенности этого последнего шаблона заключаются в том, что тело ctor может перемещаться из заголовка, и в лучшем случае генерируется линейный объем кода вместо экспоненциального количества конструкторов шаблонов.
Это решение немного менее эффективно, чем версия конструкции размещения, поскольку не все компиляторы смогут встроить использование std::function
. Но он также позволяет хранить неподвижные объекты.
Код не тестировался, возможно, есть опечатки.
В c++17 с гарантированным исключением необязательная часть задержанного ctor становится устаревшей. Любая функция, возвращающая T
, — это все, что вам нужно для отложенного ctor T
.
person
Yakk - Adam Nevraumont
schedule
26.04.2016
std::foward
, не так ли? - person Unda   schedule 26.04.2016Loss
иOptimizer
. Лучший ответ зависит от таких деталей, как:Loss
иOptimizer
дорого копировать, но дешево перемещать? Устраивают ли авторов и сопровождающих этот код ограничения шаблонов (e.g. enable_if
)? Решение с передачей по значению иногда подходит. Если вы выберете эталонное решение для пересылки, я настоятельно рекомендую правильно ограничить его. Если только один изLoss
иOptimizer
можно дешево переместить, можно рассмотреть гибридное решение. - person Howard Hinnant   schedule 26.04.2016Loss
иOptimizer
должны быть выведены типами. - person Quentin   schedule 26.04.2016_loss(loss)
. Даже еслиloss
имеет типLoss&&
, этот инициализатор все равно будет рассматриватьloss
как lvalue. Это важно, хотя и неинтуитивно. @ Федерико, у тебя было впечатление, что_loss(loss)
переедет изLoss&& loss
? Фактически, он будет скопирован. - person Aaron McDaid   schedule 26.04.2016const &
на&&
для этого примера. Мой код немного сложнее этого, и я не хотел заполнять вопрос лишними вещами, но спасибо за участие! - person Federico Allocati   schedule 26.04.2016_loss
и_optimizer
или ссылками? - person M.M   schedule 27.04.2016LinearClassifier(const Loss& loss, const Optimizer& optimizer)
. Случаи со ссылкой на rvalue предполагают, что вы, возможно, захотите отказаться от аргументов... но тогда вы фактически не использовалиstd::move
в аргументах - person M.M   schedule 27.04.2016Loss&&
, также может связываться сconst Loss&
, и поэтому нет смысла писать перегрузкуLoss&&
, если вы не сделаете что-то другое в инициализаторах, отличное отconst Loss&&
. Думаю, спрашивающий это понимает, и понимает, что это просто игрушечный код в вопросе. Я бы предпочел более реалистичный код, так как этот игрушечный код действительно сбивает с толку. - person Aaron McDaid   schedule 27.04.2016Loss&&
для завершения, потому что в какой-то момент какой-то типLoss
может быть дорогим для копирования. - person Federico Allocati   schedule 27.04.2016