Posts tagged c++11

Lambda: o Retorno!

3

Lambda: o Retorno

Na última vez que foi abordado o tema "lambda na ferida" falamos brevemente sobre como C++ agora permite criar funções dentro de funções. Hoje vamos apenas falar que aquela construção bizarra que criamos fica ainda mais bizarra se precisarmos retornar alguma coisa dessa função ou usá-la mais de uma vez.

O padrão do lambda é supor que sua função embutida e enlatada não precisa retornar nada, o que torna a sintaxe mais simples: é um void AlgumaCoisa(argumentos). No entanto, para algoritmos como o find_if isso não funciona, então é necessário retornar algo. E, no caso de find_if, chamá-lo mais de uma vez pode ser feito facilmente criando uma variável lambda:

#include "Common.h"
#include <algorithm>
#include <vector>
#include <string>
 
 
int main()
{
	std::vector<Employee> employees; // um bando de empregados
	std::string currentDate = GetCurrentDate();
 
	// definindo uma função, como quem não quer nada, dentro de uma função
	auto FindByBithDate = [&](Employee& employee)->bool // <-- tipo de retorno
	{
		return employee.birthDate == currentDate;
	};
 
	GetEmployees(employees);
 
	auto findIt = std::find_if(employees.begin(), employees.end(), FindByBithDate);
 
	while( findIt != employees.end() )
	{
		SendMail(*findIt);
		findIt = std::find_if(findIt + 1, employees.end(), FindByBithDate);
	}
}

O tipo de retorno que colocamos através de uma flechinha é obrigatória? De fato, não. Se eu omiti-la vai funcionar do mesmo jeito porque o único ponto de saída da minha função retorna um bool.

Esses compiladores estão ficando cada vez mais espertos.

A moda agora é levar lambda na função

5

moda-lambda

A nova moda de programar C++ nos últimos anos com certeza é usar lambda. Mas, afinal, o que é lambda? Bom, pra começar, é um nome muito feio.

O que esse nome quer dizer basicamente é que agora é possível criar função dentro de função. Não só isso, mas passar funções inteiras, com protótipo, corpo e retorno, como parâmetro de função.

Isso significa que finalmente os algoritmo da STL vão ser úteis e não um "pain in the ass".

Por exemplo, antes, tínhamos que fazer o seguinte malabarismo para mexer com arrays/vetores/listas:

#include "Common.h"
#include <algorithm>
#include <vector>
#include <string>
 
 
void NewYearMail(Employee& employee)
{
	// isso s? tem duas linhas, mas poderia ter... 10!
	employee.age += 1;
	SendMail(employee);
}
 
 
int main()
{
	std::vector<Employee> employees; // um bando de empregados
	GetAnniversaryEmployees(employees);
	std::for_each(employees.begin(), employees.end(), NewYearMail);
}

Imagine que para cada interação devíamos criar uma função que manipulasse os elementos do vetor.

Uma alternativa que costumava utilizar era a de roubar na brincadeira e criar um tipo dentro da função (permitido) e dentro desse tipo criar uma função (permitido):

#include "Common.h"
#include <algorithm>
#include <vector>
#include <string>
 
 
int main()
{
	struct NewYearMail { void operator()(Employee& employee)
	{
		employee.age += 1;
		SendMail(employee);
	}}; // note o final com chaves-duplas, fechando a fun??o e a struct
 
	std::vector<Employee> employees; // um bando de empregados
	GetAnniversaryEmployees(employees);
	std::for_each(employees.begin(), employees.end(), NewYearMail());
}

Apesar disso gerar INTERNAL_COMPILER_ERROR em muitos builds com o Visual Studio 2003 (e o rápido, mas anos noventa, Visual Studio 6) na maioria das vezes o código compilava e rodava sem problemas. No entanto, deixava um rastro sutil de gambi no ar...

Agora isso não é mais necessário. Desde o Visual Studio 2010 (que eu uso) a Microsoft tem trabalhado essas novidades do padrão no compilador, e aos poucos podemos nos sentir mais confortáveis em usar essas modernices sem medo. Por exemplo:

#include "Common.h"
#include <algorithm>
#include <vector>
#include <string>
 
 
int main()
{
	std::vector<Employee> employees; // um bando de empregados
	GetAnniversaryEmployees(employees);
 
	std::for_each(employees.begin(), employees.end(), [&](Employee& employee)
	{
		employee.age += 1;
		SendMail(employee);
	}); // note o final com chave e par?ntese, fechando a fun??o e a chamada do for_each
}

"Caraca, mas o que é esse código alienígena?", diria alguém como eu alguns anos atrás (talvez até meses). Bom, nada vem de graça em C++ e dessa vez houve algumas mudanças meio drásticas na sintaxe para acomodar o uso dessa lambida inline.

#include <algorithm>
#include <iostream>
 
int main()
{
	int arrayDeInts[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 
	std::for_each( // estou chamando uma fun??o aqui
		&arrayDeInts[0], &arrayDeInts[10], // come?o e final (begin e end para STL)
		[&] // opa! sintaxe nova. os [] dizem que come?a uma fun??o lambida e 
			//o & diz que posso acessar todo mundo
 
		(int umInt) // depois dos []s vem o formato de recebimento de par?metros de uma fun??o
			// (no caso, como ? um array de ints, o par?metro para o for_each tem que ser um int)
 
		{ // iniciamos a fun??o dentro da chamada do for_each
 
			std::cout << umInt << ' '; // podemos acessar umInt, arrayDeInts 
			// e qualquer vari?vel da fun??o ou objeto (se fosse um objeto)
 
		} // finalizamos a fun??o dentro da chamada do for_each
 
	); // e precisamos fechar a chamada do for_each depois dessa suruba toda
 
}

E não é só isso. Tem muito mais esquisitices de onde veio essa.

The-C-Programming-Language

RValue é o novo LValue

2

As grandes discussões filosóficas que participei durante meu estudo da linguagem C, e mais tarde de C++, muitas vezes convergiam para o significado místico daquela figura que nós da gramática da linguagem conhecemos como lvalue, ou l-value, ou left-value. Enfim, a definição de uma expressão que representa um lugar na memória e, portanto, pode ocupar o lado esquerdo de uma atribuição/cópia/passagem de argumentos qualquer. Porém, os "grandes" embates daquela época hoje parecem brincadeira de criança, como a diferença sutil entre ++x e x++ ou convergência de tipos em templates.

Agora o buraco é mais embaixo. Agora temos referências r-value.

Agora o mundo mudou.

Foi necessário que mudasse. C++, conhecido internacionalmente como a vanguarda das linguagens, mesmo mantendo sua fama de alta performance, precisava voltar às suas origens performáticas de qualquer forma. O Criador da linguagem e seus seguidores estavam cientes: cópia de strings é uma coisa muito, muito má. Imperfect forwarding (direcionamento imperfeito?) é algo ainda pior, pois é mais sutil.

Todos concordam, então, que a mudança é necessária. Nem todos concordam, contudo, com o preço a ser pago. As coisas começam a ficar cada vez mais difíceis de entender, e agora, com r-values vindo à superfície, o universo de criaturas bizarras volta a mostrar as caras.

Desde o começo de meus estudos em C++ tenho admirado a linguagem com um certo distanciamento. Enquanto a linguagem C continua sendo o supra-sumo das linguagens de médio-nível, C++ continua sendo uma abominação cujos detalhes muitos preferem esquecer. Mas esquecer tem se tornado cada vez mais difícil frente às gambiarras adaptações técnicas que a linguagem vem sofrendo.

No caso de Rvalues, se antes existia uma discussão interminável sobre sua inclusão no novo padrão, agora existem discussões acerca do que tudo isso significa. Existe até um ótimo guia (thanks to pepper_chico) sobre as principais mudanças de conceitos, feito para simplificar o entendimento. Mas ele mesmo é exageradamente complexo para o programador médio. É de forçar a barra, mesmo. É pedir demais.

Conversemos

No próximo dia 28, sábado, nos reuniremos em mais um evento C++ organizado pela Microsoft pelo Grupo C/C++ Brasil e pelos agora dois MVPs do Brasil, o veterano Fabio Galuppo e o novato Rodrigo Strauss (meu amigo, mas acima de tudo muito bem-vindo ao cargo). Estou na lista de palestrantes e conversarei com vocês sobre as otimizações que o famigerado RValue deve trazer à mesa. Espero conseguir entender um pouco mais sobre essa criatura fantástica até lá.

Se o Cebolinha for um programador C++, deve estar se debatendo nesse momento.

Linques úteis

Go to Top