C++

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.

remove_if até remove, só que diferente

7

A surpresa de hoje foi descobrir (vejam só) que o remove_if, como todo algoritmo da STL, deve ser olhado de perto antes de usado. Nesse caso em específico porque, apesar do nome, a função NÃO remove elementos, mas os sobrescreve.

Imagine uma função que usa remove_if para remover todas as idades de potenciais lolitas:

void RemoveIfLolita(vector<int>& ages)
{
	remove_if(ages.begin(), ages.end(), [&](int age) { return age < 18; } );
}

Ou até sua contraparte usando um array C:

void RemoveIfLolita(int* ages, int size)
{
	remove_if(ages, ages + size, [&](int age) { return age < 18; } );
}

Um uso trivial pode não cuspir um resultado trivial, ou seja, os elementos não serão removidos como se espera:

#include <algorithm>
#include <iostream>
#include <vector>
 
using namespace std;
 
 
void RemoveIfLolita(int* ages, int size)
{
	remove_if(ages, ages + size, [&](int age) { return age < 18; } );
}
 
void RemoveIfLolita(vector<int>& ages)
{
	remove_if(ages.begin(), ages.end(), [&](int age) { return age < 18; } );
}
 
 
int main()
{
	vector<int> ages;
 
	ages.push_back(10);
	ages.push_back(21);
	ages.push_back(66);
	ages.push_back(18);
	ages.push_back(16);
	ages.push_back(15);
	ages.push_back(8);
	ages.push_back(24);
	ages.push_back(12);
	ages.push_back(20);
	ages.push_back(13);
	ages.push_back(13);
 
	RemoveIfLolita(ages);
	cout << "Vector (" << ages.size() << "):\n";
	for_each(ages.begin(), ages.end(), [&](int age) { cout << age << endl; });
 
	int newAges[] = { 10, 21, 66, 18, 16, 15, 8, 24, 12, 20, 13, 13 };
	const int newAgesSz = (int) ( sizeof(newAges) / sizeof(int) );
	RemoveIfLolita(newAges, newAgesSz);
	cout << "\n\nArray (" << newAgesSz << "):\n";
	for_each(newAges, newAges + newAgesSz, [&] (int age) { cout << age << endl; } );
}

RemoveIfErrado

Isso ocorre porque o comportamento do remove_if é copiar todos os elementos que retornem false (não remova) e pular elementos que retornem true (remova). No entanto, o tamanho do contêiner, e consequentemente seu ponteiro end(), permanecem o mesmo.

RemoveIfComportamento

De acordo com o saite cplusplus.com, o algoritmo STL é previsível, simples, e por isso mesmo sujeito a otimizações do compilador:

template <class ForwardIterator, class UnaryPredicate>
ForwardIterator remove_if (ForwardIterator first, ForwardIterator last,
	UnaryPredicate pred)
{
	ForwardIterator result = first;
	while (first!=last) {
		if (!pred(*first)) {
			*result = *first;
			++result;
		}
		++first;
	}
	return result;
}

Para obtermos qual seria o "novo end()", precisamos obter esse valor do retorno de remove_if. Com base nisso, podemos alterar o tamanho do contêiner ajustado:

#include <algorithm>
#include <iostream>
#include <vector>
 
using namespace std;
 
 
int RemoveIfLolita(int* ages, int size)
{
	auto newEnd = remove_if(ages, ages + size, [&](int age) { return age < 18; } );
	return newEnd - ages;
}
 
void RemoveIfLolita(vector<int>& ages)
{
	auto newEnd = remove_if(ages.begin(), ages.end(), [&](int age) { return age < 18; } );
	ages.resize(distance(ages.begin(), newEnd));
}
 
 
int main()
{
	vector<int> ages;
 
	ages.push_back(10);
	ages.push_back(21);
	ages.push_back(66);
	ages.push_back(18);
	ages.push_back(16);
	ages.push_back(15);
	ages.push_back(8);
	ages.push_back(24);
	ages.push_back(12);
	ages.push_back(20);
	ages.push_back(13);
	ages.push_back(13);
 
	RemoveIfLolita(ages);
	cout << "Vector (" << ages.size() << "):\n";
	for_each(ages.begin(), ages.end(), [&](int age) { cout << age << endl; });
 
	int newAges[] = { 10, 21, 66, 18, 16, 15, 8, 24, 12, 20, 13, 13 };
	int newAgesSz = (int) ( sizeof(newAges) / sizeof(int) );
	newAgesSz = RemoveIfLolita(newAges, newAgesSz);
	cout << "\n\nArray (" << newAgesSz << "):\n";
	for_each(newAges, newAges + newAgesSz, [&] (int age) { cout << age << endl; } );
}

RemoveIfFunciona

Esse C++... intuitivo como nunca!

logo-sun

Uma nova linguagem

4

 

Tenho que me atualizar. Faz um tempo (anos) em que deixei de lado esse mundo "frescurento" de C++2030 e me foquei única e exclusivamente em resolver problemas da melhor forma possível com o que a linguagem já tinha a oferecer em uma implementação estável de compilador e bibliotecas.

Agora o mundo está mudando. Para quem é do Universo Windows/Microsoft, a empresa do Uncle Bill vem liberando algumas versões interessantes do seu compilador (VS2012, 2013 e agora o CTP), cada vez mais próxima de um C++11/14 100% compliance. Não acredito que cheguem lá, mas o fato de estarem empenhados indica para a indústria e seus clientes que há uma demanda sendo atendida. Não é mais frescurite de acadêmicos. Algumas features ultra-novas começam a ser usadas e permitidas em projetos.

Estamos falando de uma nova linguagem que se forma com um novo ritmo. O padrão C++11 demorou "apenas" 2 anos para cair em nossas linhas de comando, há um patch já confirmado para o ano que vem e já existem menções para um novo release em 2017. Para o programador C++ que se acostumou a contar as evoluções em décadas, um novo ritmo se impõe. Não há tempo para cristalização de conceitos. O boost já nos forneceu isso por todos esses anos e hoje ele é reconhecidamente a versão alpha que precisávamos.

Veremos o que o futuro cada vez mais presente nos reserva.

ShowCast

MVP ShowCast 2013: C++11 e C++14 no Visual Studio 2013

4

ShowCast

Uma notícia meio relâmpago (leia "publicada tarde demais"): amanhã (Segunda-Feira, 02/12/2013) às 17:00 (horário de Brasília) será exibida uma web-palestra web-ao vivo pelo web-Strauss e moderada por web-mim. O assunto é: todas as bugingangas novas que o recém-chegado Visual Studio 2013 suporta da (praticamente) nova linguagem C++11 (e seu amigo do futuro, C++14).

Estarei me guiando pela tabela da MSDN que relaciona essas novidades entre as diferentes versões do Visual Studio. São muitas mudanças de uma vez só, e pode haver detalhes obscuros que eu ainda não ouvi falar, mas tentarei obter o máximo de informações possíveis para juntos tentarmos extrair o que pudermos nessa 1 hora e 15 minutos de web-interatividade.

Quer se inscrever? Acesse o web-linque do evento e nos aguarde por lá!

Update

 

O evento foi realizado e gravado e em breve tanto o áudio quanto os slides estarão disponíveis. Foi uma explicação sucinta sobre as novidades da linguagem, mas muito bem explicada por Strauss, o que deve ajudar a galera a se atualizar aos poucos.

Não foi uma palestra muito popular, talvez pelo dia/horário, mas tivemos a participação de cerca de 7 pessoas. A avaliação final não reflete isso, mas pudemos contar com uma série de perguntas pertinentes do participante Andre.

PoolVS2013CPP11

 

Update 10/12/2013

 

Seguem os slides da palestra:

Untitled2

10° Encontro de Programadores de C & C++

4

Ando tendo alguns problemas de postagem no meu blog, por isso o aviso não foi feito com mais antecedência. Peço desculpas aos organizadores do evento, pois sei que todo tipo de divulgação é útil.

Chegamos em mais um evento do grupo C/C++ Brasil, dessa vez honrando a parte "Brasil" do nome. Sim, nosso próximo evento será fora de Sampa, mas ainda próximo, no Rio de Janeiro! Até onde eu sei, o primeiro que se tem notícia. Finalmente o grupo terá a chance de se reunir na terra de programadores C++ de referência internacional como Pedro Lamarão.

Os detalhes do evento estão, logicamente, no saite oficial do grupo. Ele ocorrerá no dia 25 de maio (ainda dá tempo de comprar passagem) e terá sua programação divulgada já em abril. Infelizmente o tempo para o call for papers quase se esgotou (vai até dia 30 desse mês).

Enfim, essa é a chance de intercâmbio esperada entre nossa comunidade de programadores C/C++ de outras partes do país e que ainda não tiveram a oportunidade de participar dos nossos tradicionais encontros.

Correção: esse encontro foi o décimo, diferente do inicialmente proposto. Ou mudamos a base para 8 =P

Atualização: o encontro rolou, pelos comentários foi bem legal e em breve teremos slides, vídeos, depoimentos e etc.

tagcloud

Conteúdo da Palestra

0

Uma nuvem mágica...

O evento de C++ na Microsoft (e organizada pelo nosso grupo C/C++ Brasil) aconteceu. Quem esteve lá teve o privilégio de passar algumas horas com programadores e entusiastas de ambas as linguagens e acompanhar o raciocínio dos palestrantes sobre Move Semantics, COM (good times), programação na placa gráfica e a solução para todos os problemas do universo (vulgo ZeroMQ). Fora isso, a palestra que me surpreendeu no dia foi a do Sr. Basílio Miranda, cômica e inspirada, mas sempre nos fornecendo um pouquinho mais de conhecimento acerca do Qt framework, que felizmente ainda não morreu (a Nokia recentemente liberou uma série nova que ainda usa a versão evoluída do Symbian), e graças a isso não precisaremos nos preocupar por enquanto com o destino de nosso ilustríssimo Sr. Basílio.

Fora isso tivemos uma telepalestra com um dos membro do time do Visual Studio. Mas, francamente, estou cada vez menos interessado no VS e mais no Vim. Portanto...

Segue minha palestra e meu código-fonte sobre Move Semantics devidamente compactados para apreciação dos interessados. Comentários, sugestões, dicas são sempre bem-vindos, como bem sabem os que me procuram eventualmente através de e-mails ou no blogue.

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

ExceptionSStreamConsoleOutput

Cuidado com variáveis temporárias

2

Um dos problemas que a linguagem C++ possui para seus iniciantes é o de não deixar muito explícito partes do seu comportamento, principalmente as partes que lidam com ponteiros/referências e o jogo da vida dos objetos. Às  vezes a coisa fica de tal como complexa que fica até difícil explicar o porquê das coisas.

Por exemplo, vejamos o singelo caso de alguém que precisa formatar uma saída de erro e para isso escolheu um stringstream:

#include <sstream>
#include <exception>
#include <iostream>
 
using namespace std;
 
void LogError(const char* msg)
{
    cerr << "** " << msg << endl;
}
 
 
void func()
{
    //doSomething();
    throw exception("sbrubles exception");
}
 
 
int main()
{
    try
    {
        func();
    }
    catch(exception& e)
    {
        stringstream ss;
        ss << "Error calling func: " << e.what() << endl;
        const char* errorMessage = ss.str().c_str();
        LogError(errorMessage);
    }
}
// Codigo-fonte disponivel no GitHub (Caloni/Caloni.com.br) 

Quando chamamos func, ele lança uma exceção que é capturada no main que, por sua vez, formata uma stream e obtém sua string (através do método str) e através dessa string obtém o ponteiro da string em C puro (através do método c_str). Porém, a mensagem resultante na saída-padrão de erro não era o esperado:

Depurando diretamente, vemos que a stream, de fato, contém o que esperávamos. O único elemento errante é justamente o ponteiro obtido através da chamada dupla de métodos.

O porquê isso ocorre só fica óbvio quando vemos a ajuda (ou a assinatura) da função str da classe stringstream:

string str ( ) const;
void str ( const string & s );

Get/set the associated string object
The first version returns a copy of the string object currently associated with the string stream buffer.

Ora, a função str retorna uma cópia do objeto string usado internamento pelo buffer de nossa string stream. Duas coisas ocorrem em qualquer cópia de um objeto retornada por uma função:

  • A cópia do objeto original e seu desacoplamento (óbvio).
  • A construção de um objeto baseado no original e que, após o fim da expressão onde foi chamado o método, é destruído.

Uma vez que a chamada a str termina, é entregue uma instância de uma string que contém a string original que está sendo usada pela string stream para a expressão da chamada, que geralmente vem seguida de uma cópia:

//
// 1. str retorna uma cópia;
// 2. atribuição copia retorno para buf.
//
string buf = ss.str();

A variável buf no exemplo acima será, portanto, a terceira string usada aqui até então. Ao final da expressão, a string intermediária retornada por str é automaticamente destruída, por se trata de uma cópia temporária para obedecer a sintaxe de retorno da função.

Agora, o que acontece se, na cópia temporária, é feita uma operação para obter seu ponteiro interno usado para armazenar sua string estilo C?

Obviamente ele fica inválido após o fim da expressão!

Vamos ver em câmera lenta:

Nada como assembly fresquinho para refrescar os conceitos de C++ por baixo dos panos.

Update

Após uma enxurrada de programadores gerenciáveis perguntarem qual seria, então, a solução ideal, segue o snipet mais explicitado:

// 1. Copie a string retornada para uma variável não-temporária
string buf = message.str();

// 2. Use essa string dentro de seu escopo válido (até o final do catch, no exemplo do artigo).
const char* text = buf.c_str();

Update 2

Outro leitor sugeriu fazer toda a chamada em uma única instrução, economizando em expressividade e ainda evitando a destruição da variável temporária criada ao chamar str.

// 1. Matar três coelhos com uma instrução só.
LogError(ss.str().c_str());

Particularmente, gosto de instruções simples que me permitam ver claramente o que está acontecendo de forma simples pelo depurador (até porque sei que o compilador irá otimizar tudo no final em versão Release, ainda mais se estiver quebrado em instruções simples). Porém, toda solução que evita o uso da variável temporária após a execução do método str é válida.

Sem reflection

2

Em C++ não temos (ainda) a possibilidade de listarmos, por exemplo, a lista de métodos de um determinado tipo, a fim de chamá-lo pelo nome em tempo de execução. Algo assim:

class MyClass
{
public:
        void Method1();
        void Method2();
        void Method3();
};
 
 
int main()
{
        MyClass c;
        if( auto m = typeid(c).methods.getaddresof("Method1") )
                m();
}
// Codigo-fonte disponivel no GitHub (Caloni/Caloni.com.br) 

OK, foi apenas um exemplo tosco de como seria um reflection em C++.

Porém, existem algumas maneiras de contornar esse problema. A solução, é claro, depende de qual problema você está tentando resolver.

Vamos supor, por exemplo, que você queira cadastrar funções para serem chamadas de maneira uniforme pelo prompt de comando. Vamos chamar nossa classe tratadora de CommandPrompt.

typedef void (Method*)(vector<string>& args);
 
class CommandPrompt
{
public:
        void Add(string name, Method m); // adiciona novo m?todo
        void Interact(ostream& os, istream& is); // come?a intera??o com usu?rio
};
// Codigo-fonte disponivel no GitHub (Caloni/Caloni.com.br) 

Internamente, para armazenar as funções de acordo com o nome dado, basta criarmos um mapeamento entre esses dois tipos e fazemos a amarração necessária para o método principal de parseamento:

typedef map<string, Method> MethodList; // uma vari?vel desse tipo armazena todas as fun?es
 
void CommandPrompt::Interact(ostream& os, istream& is)
{
        while( is )
        {
                string func;
                vector<string> args;
 
                if( ParseLine(is, func, args) )
                {
                        // se a fun??o desejada est? em nossa lista,
                        // podemos cham?-la, mesmo sem conhecer qual ?
                        if( Method m = m_funcs[func] )
                                m(args);
                }
        }
}
// Codigo-fonte disponivel no GitHub (Caloni/Caloni.com.br) 

Essa solução não é exatamente um reflection, mas apenas parte do que o verdadeiro reflection possibilita. Existem outras funcionalidades, como traits, que a STL já consegue se virar razoavelmente bem, por exemplo.

Go to Top