Este capítulo mostra um recurso pequeno do C++ moderno. Algo simples, familiar para programadores de outras linguagens, mas que pode ter impacto gigante, devidamente utilizado, em bases de código legadas de difícil legibilidade. Trata-se da palavra-chave auto – algo semelhante a var para programadores C# e Java, mas bem mais poderoso em C++.
auto é tão bem-aceito, simples e impactante que Herb Sutter, um dos ícones de C++, que trabalha na Microsoft, evangeliza seu uso de maneira intensiva.
Os compiladores C++ estão ficando mais amigáveis
Historicamente, o principal compromisso dos compiladores C++ é gerar o melhor executável possível a partir do código-fonte fornecido pelo desenvolvedor. Os compiladores não criticam você, tampouco julgam suas motivações. Eles simplesmente assumem que você sabe (ou, pelo menos, deveria saber) o que está fazendo!
Um bom exemplo de como compiladores C++ modernos, e a própria linguagem, tem adaptado sua filosofia é o suporte amplo a palavra-chave auto – uma abstração, com custo zero, para que seja identificado, a partir do código, o tipo que o desenvolvedor intenciona usar.
auto no C++11
A partir do C++11, auto passou a ser uma alternativa na declaração de variáveis locais.
auto a = 42; // 'a' type is integer.
auto b = 27.5; // 'b' type is double.
auto c = "Elemar"; // 'c' type is char const *
auto d = {4, 5, 6}; // 'd' type is std::initializer_list<int>
auto f = [](unsigned char c) { return std::toupper(c); };
auto o programador transfere para o compilador a responsabilidade de inferir o tipo apropriado para as variáveis (da mesma forma como var ajuda em linguagens como Java e C#). Isso reduz bastante a verbosidade dos códigos, sem custos.Eventualmente, a escolha padrão de tipos do compilador poderá não ser a desejada. No exemplo acima, poderia ser desejável que a variável c fosse declarada como std::string, no lugar de char const *. Ou ainda, a variável d ficaria melhor como std::vector<int> no lugar de std::initializer_list<int>. Nestes casos, uma alternativa é usar a sintaxe de construção auto n = type-name { expression } , como segue:
auto c = std::string {"Elemar"};
auto d = std::vector<int> {4, 5, 6};
Simples, mas efetivo.
Trailing Return Types
Até aqui, auto em C++ parece, muito, com var de Java e C#. Mas, como já foi dito, auto vai muito além.
A partir do C++11, há um novo formato para declaração de funções: usando trailing return types. Trata-se de uma sintaxe alternativa de declaração de funções que permite que o tipo de retorno seja especificado depois da lista de parâmetros.
// C++98
int foo(int a, int b) {
// ..
}
// C++11
auto foo(int a, int b) -> int {
// ..
}
Na nova forma de declaração, o tipo de retorno é especificado após a lista de parâmetros, e não antes. A palavra-chave auto, aqui, não é responsável por qualquer tipo de inferência, apenas indicativo de que o tipo de retorno será especificado depois.
A principal motivação para a nova notação é a possibilidade de ampliar o poder de declaração de templates.
C++98
template <typename L, typename R>
decltype (std::declval<L>() + std::declval<R>()) foo(L& a, R& b) {
// ..
}
// C++11
template <typename L, typename R>
auto foo(L& a, R& b) -> decltype(a + b) {
// ..
}
A segunda sintaxe, usando a ideia trailing return type, é possível, sabendo que o compilador processa código da esquerda para a direita, porque a lista de parâmetros já foi declarada e a e b já foram descobertos.
auto no C++14
A partir do C++14, auto pode ser usado, também, na declaração de parâmetros em funções lambda.
auto add = [](auto const a, auto const b) { return a + b; };
Além disso, a linguagem agora também suporta a utilização de auto em funções que não utilizem trailing return types.
auto foo(int a, int b) {
// ..
}
Simplesmente tente criar um código como o que segue em C# ou Java.
auto add(auto a, auto b) {
return a + b;
}
int main() {
std::cout
<< add(2, 3) << std::endl
<< add(std::string{"Elemar"}, "Jr");
}
Benefícios de usar auto sempre que possível
A utilização de auto impede que programadores C++ deixem variáveis sem inicialização – um engano comum entre programadores, mesmo experientes. Além disso, garante que um tipo mais apropriado (correto?!) seja associado a uma variável, impedindo a ocorrência de conversões implicitas ou perda de precisão.
auto vec = std::vector {1, 2, 3};
int count1 = vec.size(); // possible conversion loss
auto count2 = vec.size(); // 'count2' type is size_t;
Na prática, auto permite a escrita de menos código (ainda menos do que var autoriza em C# e Java), reduzindo preocupações com tipos que não são realmente importantes (como iterators, por exemplo).
std::map<int, std::string> m;
// C++98
for (std::map<int, std::string>::const_iterator it = m.cbegin();
it != m.cend();
++it)
{
//
}
// C++11
for (auto it = m.cbegin(); it != m.cend(); ++it)
{
//..
}
Mas nem tudo são flores…
A utilização de auto em C++, embora mais poderosa que var de C# e Java, é mais perigosa também.
auto detecta apenas o tipo a partir de uma expressão, ignorando se o valor é uma constante (const) ou volátil (volatile). Também ignora indicadores de referência ou apontamento.
#include <iostream>
class Envelope {
int _data;
public:
Envelope(int const data = 0) : _data { data }
{}
int& get() { return _data; };
};
int main() {
Envelope instance(42);
auto x = instance.get();
auto& y = instance.get();
auto z = x;
auto& w = y;
x = 10;
y = 11;
z = 12;
w = 13;
std::cout
<< instance.get() << " "
<< x << " " << y << " " << z << " " << w;
// 13 10 13 12 13
}
No código acima, por exemplo, x e z são do tipo int e não int&.
Uma alternativa, mais segura, quando o objetivo de uma expressão é criar variáveis com tipos idênticos – considerando constância, volatilidade, apontamento e referências – de outras variáveis é o uso de decltype.
int main(){
Envelope instance(42);
auto& x = instance.get();
decltype(x) y = instance.get();
decltype(x) z = x;
decltype(x) w = y;
x = 10;
y = 11;
z = 12;
w = 13;
std::cout
<< instance.get() << " "
<< x << " " << y << " " << z << " " << w;
// 13 13 13 13 13
}
Novamente, nada facilmente encontrado em outras linguagens!
Para pensar!
C++ moderno inclui uma série de facilitadores e está mais expressiva – auto é um bom exemplo! Entretanto, a linguagem não perde sua característica de maximizar poder, eventualmente, sacrificando “segurança”.
Recursos isolados de C++ não previnem enganos. Entretanto, a combinação de recursos modernos torna a ocorrência deles cada vez mais rara.