Dans quelle mesure vers le bas le trou de lapin voulez-vous aller?
Je suis conscient de 4 décent façons d'aborder ce problème. Vous devez utiliser généralement les plus anciennes si vous correspondez à leurs conditions, comme chaque plus tard, on l'augmente de manière significative dans la complexité.
Pour la plupart, soit déplacer est donc pas cher de le faire deux fois, c'est gratuit, ou de déplacer la copie.
Si un déménagement est la copie, et la copie est non-libre, prenez le paramètre par const&
. Si pas, le prendre par la valeur.
Cela se comportent exactement de manière optimale, et de votre code beaucoup plus facile à comprendre.
LinearClassifier(Loss loss, Optimizer const& optimizer)
: _loss(std::move(loss))
, _optimizer(optimizer)
{}
pour un bon marché à déplacer Loss
et déplacez-est-copie optimizer
.
Ce n'1 extra déplacer sur la "optimale" de transfert parfait ci-dessous (remarque: le transfert parfait n'est pas optimale) par la valeur du paramètre dans tous les cas. Tant que déplacer n'est pas cher, c'est la meilleure solution, car il génère des propres messages d'erreur, permet aux {}
en fonction de la construction, et est beaucoup plus facile à lire que les autres solutions.
Envisager l'utilisation de cette solution.
Si un déménagement est moins cher que la copie mais non-libre, une approche de transfert parfait en fonction:
Soit:
template<class L, class O >
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))
{}
Ou la plus complexe et plus la surcharge de l'environnement:
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))
{}
cela vous coûte la capacité à faire, {}
en fonction de la construction de vos arguments. Aussi, jusqu'à nombre exponentiel de constructeurs peut être généré par le code ci-dessus s'ils sont appelés (j'espère qu'ils seront inline).
Vous pouvez déposer l' std::enable_if_t
clause au prix de SFINAE échec; en gros, le mal de surcharge de votre constructeur peut être choisi si vous n'êtes pas prudent avec ce std::enable_if_t
de la clause. Si vous avez des surcharges de constructeur avec le même nombre d'arguments, ou de se soucier de début de l'échec, alors vous voulez l' std::enable_if_t
on. Sinon, utilisez le plus simple.
Cette solution est généralement considéré comme "optimal". Il est accepably optimal, mais il n'est pas le plus optimal.
La prochaine étape consiste à utiliser emplace construction avec des n-uplets.
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)
)
{}
où nous reporter la construction jusqu'à l'intérieur de l' LinearClassifier
. Cela vous permet d'avoir non-copier/objets mobiles dans votre objet, et est sans doute une efficacité maximale.
Pour voir comment cela fonctionne, exemple maintenant, piecewise_construct
travaille avec std::pair
. Vous passez par morceaux construisons d'abord, puis forward_as_tuple
les arguments pour construire chaque élément de la suite (y compris la copie ou le déplacement ctor).
En construisant directement des objets, nous pouvons éliminer un déménagement ou une copie par objet par rapport à la parfaite solution de transfert ci-dessus. Il vous permet également de transférer une copie ou un déplacement si nécessaire.
Une finale mignon technique est de type effacement de la construction. En pratique, il faut quelque chose comme std::experimental::optional<T>
à être disponible, et pourrait faire de la classe un peu plus grand.
C'est pas plus rapide que le cas de la construction d'un. Il n'résumé le travail que l'emplace la construction d'un, ce qui rend plus simple sur une base unitaire, et il vous permet de diviser ctor corps à partir du fichier d'en-tête. Mais il y a une petite quantité de surcharge, à la fois de l'exécution et de l'espace.
Il ya un tas de standard vous avez besoin pour commencer avec. Cela génère une classe de modèle qui représente le concept de "construction de l'objet, plus tard, à l'endroit de quelqu'un d'autre va me dire."
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; }
};
où nous type d'effacement à l'action de la construction d'une option à partir des arguments arbitraires.
LinearClassifier( delayed_construct<Loss> loss, delayed_construct<Optimizer> optimizer ) {
loss(_loss);
optimizer(_optimizer);
}
où _loss
sont std::experimental::optional<Loss>
. Pour supprimer le caractère facultatif de l' _loss
vous devez utiliser std::aligned_storage_t<sizeof(Loss), alignof(Loss)>
et d'être très prudent sur l'écriture d'un ctor pour gérer les exceptions manuellement et de détruire les choses etc. C'est un mal de tête.
Quelques belles choses à propos de ce dernier modèle est que le corps de la ctor peut sortir de la tête, et à plus d'un linéaire de la quantité de code est généré à la place d'une exponentielle de la quantité de modèle de constructeurs.
Cette solution est légèrement moins efficace que le placement de construire version, comme tous les compilateurs seront en mesure de inline l' std::function
d'utilisation. Mais elle permet aussi de stocker des non-objets mobiliers.
Code non testé, donc il y a probablement des fautes de frappe.