web-dev-qa-db-fra.com

make_unique avec initialisation de la corde

https://fr.cppreference.com/w/cpp/memory/unique_ptr/make_unique Écrit que std::make_unique peut être mis en œuvre comme

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Cela ne fonctionne pas pour les structures simples sans constructeurs. Ceux-ci peuvent être initialisés par la corset mais n'ont pas de constructeur non par défaut. Exemple:

#include <memory>
struct point { int x, z; };
int main() { std::make_unique<point>(1, 2); }

Compiler ceci aura le compilateur de l'absence de constructeur de 2 arguments et à juste titre.

Je me demande, y a-t-il une raison technique de ne pas définir la fonction en termes d'initialisation de la corset à la place? Un péché

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
}

Cela fonctionne assez bien pour le scénario ci-dessus. Y a-t-il d'autres cas d'utilisation légitime que cela va-t-il casser?

Voyant comment la tendance générale semble préférer les accolades à l'initialisation, je supposerais faire des accolades dans ce modèle serait le choix canonique, mais le fait que la norme ne le fasse pas pourrait être une indication de moi manquant quelque chose.

18
MvG

Certaines classes ont un comportement différent avec les 2 styles d'initialisation. par exemple.

std::vector<int> v1(1, 2); // 1 element with value 2
std::vector<int> v2{1, 2}; // 2 elements with value 1 & 2

Il n'ya peut-être pas suffisamment de raisons de choisir une préférence à une autre; Je pense que la norme choisit simplement une et indiquez la décision explicitement.

En tant que solution de contournement, vous voudrez peut-être mettre en œuvre votre propre make_unique version. Comme vous l'avez montré, ce n'est pas un travail acharné.

10
songyuanyao

En C++ 20, cela compilera:

std::make_unique<point>(1, 2);

en raison de la nouvelle règle permettant d'initialiser les agrégats d'une liste de valeurs parententisée .


En C++ 17, vous pouvez simplement faire:

std::unique_ptr<point>(new point{1, 2});

Cela ne fonctionnera pas avec make_shared bien que. Donc, vous pouvez également créer une usine (transfert à gauche comme exercice):

template <typename... Args>
struct braced_init {
    braced_init(Args... args) : args(args...) { }
    std::Tuple<Args...> args;

    template <typename T>
    operator T() const {
        return std::apply([](Args... args){
            return T{args...};
        }, args);
    }
};

std::make_unique<point>(braced_init(1, 2));

En C++ 14, vous devrez mettre en œuvre apply et écrire une fonction d'usine pour braced_init Parce qu'il n'y a pas encore de CTAD - mais celles-ci sont faisables.


Voir comment la tendance générale semble préférer les accolades pour l'initialisation

Citation requise. C'est un sujet chargé - mais je suis absolument en désaccord avec la demande.

8
Barry

En plus d'autres réponses, dans son Présentation sur C++ 17, Alisdair Meredith donne la mise en œuvre suivante de make_unique:

template<typename T, typename... Args>
auto make_unique(Args&&... args) -> std::unique_ptr<T> {
    if constexpr (std::is_constructible<T, Args...>::value)
        return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
    else
        return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
}

Il utilise C + 17 if constexpr, mais peut facilement être réécrit sans elle.

Avec cette version, vous pouvez faire les deux

auto v = make_unique<std::vector<int>>(10, 20); // *v is a vector of 10 elements

et

auto p = make_unique<point>(10, 20); // *p is a point (10, 20)
1
Evg