Encaminhamento Perfeito
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy. Lição 84 de 104.
Ao escrever funções de modelo que aceitam argumentos e os passam para outras funções, surge um problema: como preservar se o argumento original era um lvalue ou um rvalue? O encaminhamento perfeito resolve isso passando os argumentos exatamente como foram recebidos, mantendo sua categoria de valor.
Os ingredientes principais são referências de encaminhamento (escritas como T&& em um contexto de template) e std::forward. Uma referência de encaminhamento pode ser associada tanto a lvalues quanto a rvalues, e std::forward converte condicionalmente o argumento de volta para seu tipo original:
#include <iostream>
#include <utility>
void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg));
}
int main() {
int n = 10;
wrapper(n); // Chama process(int&) - lvalue preservado
wrapper(20); // Chama process(int&&) - rvalue preservado
}Sem std::forward, os parâmetros nomeados são sempre lvalues dentro da função, mesmo que um rvalue tenha sido passado. Isso impediria que a semântica de movimento funcionasse corretamente ao encaminhar para construtores ou outras funções.
O encaminhamento perfeito é essencial para escrever funções de fábrica, classes wrapper e qualquer código genérico que precise construir objetos ou invocar funções, preservando a intenção do chamador quanto a mover em vez de copiar.
Desafio
FácilVamos criar uma função fábrica que demonstra o encaminhamento perfeito construindo objetos enquanto preserva a categoria de valor dos argumentos passados a ela. Você criará uma função genérica make_object que encaminha os argumentos para um construtor de classe, garantindo que lvalues sejam copiados e rvalues sejam movidos.
Você organizará seu código em três arquivos:
Widget.h: Defina uma classeWidgetque rastreie como recebe seus dados.Seu
Widgetdeve armazenar um nomestd::stringe um valorint. Forneça dois construtores:- Um que aceite
const std::string&(referência lvalue) eint: imprimeWidget constructed (copy): [name] - Um que aceite
std::string&&(referência rvalue) eint: imprimeWidget constructed (move): [name]e move a string
Adicione também um método
display()que imprima[name]: [value].- Um que aceite
Factory.h: Crie sua função fábrica com encaminhamento perfeito.Escreva uma função de template
make_objectque aceite uma referência de encaminhamento para o nome e umintcomum para o valor. Usestd::forwardpara passar o argumento name ao construtor deWidget, preservando sua categoria de valor original.A função deve retornar o
Widgetconstruído por valor.main.cpp: Leia duas entradas:- Um nome (string)
- Um valor (inteiro)
Demonstre o encaminhamento perfeito criando dois widgets:
- Primeiro, armazene o nome em uma variável local e passe-o para
make_objectcomo um lvalue - Segundo, passe uma string temporária (o mesmo nome concatenado com
"_temp") diretamente paramake_objectcomo um rvalue
Depois de criar cada widget, chame seu método
display().
Por exemplo, com as entradas Gadget e 42:
Widget constructed (copy): Gadget
Gadget: 42
Widget constructed (move): Gadget_temp
Gadget_temp: 42Com as entradas Device e 100:
Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100A principal ideia é que sua função make_object não sabe se recebeu um lvalue ou rvalue, mas, ao usar std::forward, preserva essa informação ao chamar o construtor de Widget. Isso permite que a resolução de sobrecarga do construtor selecione a versão apropriada, possibilitando movimentos eficientes para temporários e cópias seguras de variáveis nomeadas.
Experimente você mesmo
#include <iostream>
#include <string>
#include "Widget.h"
#include "Factory.h"
using namespace std;
int main() {
// Ler entrada
string name;
int value;
cin >> name >> value;
// TODO: Crie o primeiro widget passando name como um lvalue
// Armazene o name em uma variável local e passe-o para make_object
// TODO: Chame display() no primeiro widget
// TODO: Crie o segundo widget passando uma string temporária como um rvalue
// Passe (name + "_temp") diretamente para make_object
// TODO: Chame display() no segundo widget
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner Genérico12Recursos Modernos do C++
Semântica de Movimentação e RvaluesEncaminhamento PerfeitoExpressões Lambda em Profundidadestd::function e std::bindconstexpr e constevalVinculações Estruturadasoptional, variant, anyPratique por conta própria: Compilador de C++ online