Programa educacional para passar parâmetros por valor para construtores e setters (C ++ moderno, exemplos)

A julgar pelos comentários habr.com/en/post/460831/#comment_20416435 no próximo post e a discussão que se desenrolou lá, o artigo sobre como transmitir corretamente argumentos ao construtor ou setter não prejudicará Habré. O StackOverflow tem muito material semelhante, mas não me lembro de algo aqui.

Porque o exemplo desse artigo está completamente correto e o autor do artigo está absolutamente certo. Aqui está um exemplo:

// . 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; }; 

Esse código permite cobrir todas (bem, quase todas) as opções possíveis para o uso da classe:

 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 com o método antigo, quando passado por const &: é definitivamente pior, porque exclui a opção (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& . 

Uma alternativa com && também é pior, mas na direção oposta:

 // . 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 

Se você não tem medo de uma explosão combinatória, pode dar uma chance a && (mas por quê? Não haverá ganho real de velocidade, o otimizador não cochila):

 //   . 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; }; 

Ou a mesma coisa, apenas com modelos (mas novamente, 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; }; 

Mesmo se você não tiver std :: string, mas algum objeto de sua própria classe grande escrita e desejar que as pessoas a movam (e não copiem), nesse caso, é melhor proibir o construtor de copiar essa classe grande do que passá-la para todos os lugares por &&. Isso é mais confiável e o código é mais curto.

Por fim, algumas opções sobre como NÃO vale a pena:

 // . 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"}; // --,  

E assim não:

 // . 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 que isso está acontecendo, qual é o princípio fundamental? É simples: um objeto, como regra, deve POSSUIR suas propriedades.

Se o objeto não quiser possuir algo, ele poderá possuir shared_ptr para esse "algo". A propósito, neste caso shared_ptr também deve ser passado por valor, e não por link constante - não há diferença com o primeiro exemplo no início do artigo:

 //  (  ). 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    

Observe: std :: move para shared_ptr é completamente legal, elimina a sobrecarga de bloquear o contador de links shared_ptr na memória (centenas de ciclos de CPU) e seu incremento. Não afeta a vida útil do objeto e outros links para ele. Mas (X) pode ser feito, é claro, apenas se o link psh no código abaixo não for mais necessário.

Moral: não use const & em geral. Olhe de acordo com as circunstâncias.

PS
Use {} em vez de () ao passar parâmetros do construtor. Juventude elegante e moderna.

PPS
Em conclusão, mais uma coisa: std :: move () na verdade não move nada e, por si só, se traduz em zero instruções do assembler. Tudo o que std :: move () faz é colocar um "rótulo adesivo" especial no link, transformando-o em uma referência de valor && -. Além disso, é possível com esse rótulo "combinar" separadamente o tipo de parâmetro de função (por exemplo, ter uma sobrecarga de função separada para o parâmetro && - e separada para o parâmetro &). O significado do rótulo && - é permitir que o código de chamada informe o chamado: “se você quiser, pode comer o valor desse link, não preciso mais dele; mas somente se você comer, deixe os ossos, então ainda preciso chamar o destruidor do esqueleto restante. " Com o mesmo sucesso, seria possível transmitir & -links comuns (você também pode usar o objeto para "comê-los"), mas com && semântica são melhores, porque você não vai confundir: onde você pode comer e onde você só pode cheirar.

Nesse sentido, o nome std :: move () deve ser reconhecido como extremamente malsucedido. Seria correto chamá-lo de std :: eat_me_if_you_want () ou std :: bon_appetit (). Mas std :: move () é mais curto.

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


All Articles