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.