Programa educativo para pasar parámetros por valor a constructores y setters (C ++ moderno, ejemplos)

A juzgar por los comentarios habr.com/en/post/460831/#comment_20416435 en la siguiente publicación y la discusión que se desarrolló allí, el artículo sobre cómo pasar correctamente los argumentos al constructor o instalador no obstaculizará a Habré. StackOverflow tiene mucho material similar, pero no recuerdo algo aquí.

Porque el ejemplo en ese artículo es completamente correcto, y el autor del artículo tiene toda la razón. Aquí hay un ejemplo:

// . struct person { person(std::string first_name, std::string last_name) : first_name{std::move(first_name)} //  , last_name{std::move(last_name)} // std::move  ! {} private: std::string first_name; std::string last_name; }; 

Dicho código le permite cubrir todas (bueno, casi todas) las opciones posibles para usar la clase:

 std::string first{"abc"}, last{"def"}; person p1{first, last}; // (1)    person p2{std::move(first), last}; // !!!    person p2{std::move(first), std::move(last)}; // (3)   person p3{"x", "y"}; //   

Compare con el método anterior, cuando se pasa por const &: definitivamente es peor, porque excluye la opción (3):

 // . struct person { person(const std::string& first_name, const std::string& last_name) : first_name{first_name} , last_name{last_name} {} private: std::string first_name; std::string last_name; }; std::string first{"abc"}, last{"def"}; person p1{first, last}; //  ,    //      ,  first  last    // ?         //   0 !  const& . 

Una alternativa con && también es peor, pero en la dirección opuesta:

 // . struct person { person(std::string&& first_name, std::string&& last_name) : first_name{std::move(first_name)} , last_name{std::move(last_name)} {} private: std::string first_name; std::string last_name; }; std::string first{"abc"}, last{"def"}; person p1{std::move(first), std::move(last)}; //  //       ,    &&  : person p2{std::string{first}, std::string{last}}; // FOOOO 

Si no tiene miedo de una explosión combinatoria, puede darle una oportunidad a && (pero ¿por qué? No habrá una ganancia real de velocidad, el optimizador no se queda dormido):

 //   . struct person { person(std::string&& first_name, std::string&& last_name) : first_name{std::move(first_name)} , last_name{std::move(last_name)} {} person(const std::string& first_name, std::string&& last_name) : first_name{first_name} , last_name{std::move(last_name)} {} person(std::string&& first_name, const std::string& last_name) : first_name{std::move(first_name)} , last_name{last_name} {} person(const std::string& first_name, const std::string& last_name) : first_name{first_name} , last_name{last_name} {} private: std::string first_name; std::string last_name; }; 

O lo mismo, solo con plantillas (pero de nuevo, ¿por qué?):

 //     (   ),      . struct person { template <typename T1, typename T2> person(T1&& first_name, T2&& last_name) : first_name{std::forward<T1>(first_name)} , last_name{std::forward<T2>(last_name)} {} private: std::string first_name; std::string last_name; }; 

Incluso si no tiene std :: string, pero algún objeto de su propia clase grande escrita, y desea que la gente haga que se mueva (y no copie), en este caso es mejor prohibir que el constructor copie de esta clase grande que pasarlo a todas partes por &&. Esto es más confiable y el código es más corto.

Finalmente, un par de opciones sobre cómo NO VALE:

 // . struct person { person(const std::string& first_name, const std::string& last_name) : first_name{first_name} , last_name{last_name} {} private: //   :  ,     //     const std::string& first_name; const std::string& last_name; }; person p{"x", "y"}; // --,  

Y así no:

 // . struct person { person(std::string& first_name, std::string& last_name) : first_name{first_name} , last_name{last_name} {} private: //   ,      shared_ptr: //  ,   std::string& first_name; std::string& last_name; }; 

¿Por qué sucede esto? ¿Cuál es el principio fundamental? Es simple: un objeto, como regla, debe PROPIETAR sus propiedades.

Si el objeto no quiere ser dueño de algo, puede ser dueño de shared_ptr para este "algo". Por cierto, en este caso shared_ptr también debe pasarse por valor, y no por enlace constante; no hay diferencia con el primer ejemplo al comienzo del artículo:

 //  (  ). struct person { person(std::shared_ptr<portfolio> pf) : pf{std::move(pf)} // std::move     {} private: std::shared_ptr<portfolio> pf; }; auto psh = std::make_shared<portfolio>("xxx", "yyy", "zzz"); ... person p1{psh}; person p2{std::move(psh)}; // (X)  ,  psh    

Tenga en cuenta: std :: move for shared_ptr es completamente legal, elimina la sobrecarga de bloquear el contador de enlaces shared_ptr en la memoria (cientos de ciclos de CPU) y su incremento. No afecta la vida útil del objeto y otros enlaces a él. Pero (X) se puede hacer, por supuesto, solo si el enlace psh en el siguiente código ya no es necesario.

Moraleja: no use const y en general. Mira según las circunstancias.

PS
Use {} en lugar de () al pasar los parámetros del constructor. De moda, moderno, juvenil.

PPS
En conclusión, una cosa más: std :: move () en realidad no mueve nada y en sí mismo se traduce en cero instrucciones de ensamblador. Todo lo que std :: move () hace es poner una "etiqueta adhesiva" especial en el enlace, convirtiéndola en una referencia de valor && - r. Y además, con esta etiqueta es posible “emparejar” por separado el tipo de parámetro de función (por ejemplo, tener una sobrecarga de función separada para el parámetro && - y un parámetro separado para &). El significado de la etiqueta && - es permitir que el código de llamada le diga a la llamada: “si lo desea, puede comer el valor de este enlace, ya no lo necesito; pero solo si comes, dejas los huesos, entonces todavía necesito llamar al destructor por el esqueleto restante ". Con el mismo éxito, sería posible transmitir enlaces & ordinarios (también puede usar el objeto para "comerlos"), pero con & & la semántica es mejor, porque no confundirá: dónde puede comer y dónde solo puede oler.

En este sentido, el nombre std :: move () debe reconocerse como extremadamente infructuoso. Sería correcto llamarlo std :: eat_me_if_you_want () o std :: bon_appetit (). Pero std :: move () es más corto.

Source: https://habr.com/ru/post/460955/


All Articles