Use 'auto' sempre que possível

Almost Always Auto
Herb Sutter

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!

No passado, compiladores C++ eram famosos por não ajudarem muito na detecção de erros no código. Aliás, eram comuns as críticas as “centenas” (exagerando, mas nem tanto) de mensagens de erro e alertas para um “ponto-e-vírgula” fora de lugar. Essa condição, entretanto, faz parte do passado, pelo menos para código escrito em um estilo moderno.
0
Considerações?x

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); };

Usando 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.
0
Concorda?x
Na prática, essa visão mais abstrata permite a evolução natural do código, diminuindo necessidade de revisões.

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.

Compartilhe este capítulo:

Compartilhe:

Comentários

Participe da construção deste capítulo deixando seu comentário:

Inscrever-se
Notify of
guest
0 Comentários
Feedbacks interativos
Ver todos os comentários

Elemar Júnior

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia. 

Desenvolvendo gente que faz a diferença

reconhecida excelência da EximiaCo, em consultorias e assessorias, aplicada no desenvolvimento de competências através de publicações e capacitações abertas e in-company.

TECH

&

BIZ

-   Insights e provocações sobre tecnologia e negócios   -   

55 51 9 9942 0609  |  me@elemarjr.com

55 51 9 9942 0609  |  me@elemarjr.com

bullet-1.png

55 51 9 9942 0609   me@elemarjr.com

0
Quero saber a sua opinião, deixe seu comentáriox
()
x