Mais uma pra quem acha que é fácil “manter” códigos em C++

Topei com essa já tem um tempo. Explico aqui também porque não gosto muito de declarar funções que não tomam parâmetros ao estilo de C++…

Considere esse simples código-fonte:

//--- test.cc ---
#include <iostream>

struct S {
  int x;

  S() { x = 1; }
  S(S& s) { x = s.x; }
};

// Declara um objeto s do tipo S construindo-o
// através de uma referência a um objeto anônimo
// criado via construdor default.
//
// Ou será que não é isso?
S s(S());

int main(void)
{
  std::cout << s.x << '\n';
}

Veja que s é declarado chamando o construtor S(S&) e como parâmetro desse construtor usamos S(), criando um objeto anônimo da classe S… Lembrando que um objeto anônimo é um que foi criado sem um nome (lógico, né?) e será destruído automaticamente logo após seu uso…

Agora, dê uma olhada na seguinte declaração:

int f();

Isso é o protótipo de uma função f que não toma parâmetros e devolve um inteiro. Não definimos a função, apenas a declaramos… Repare que, se tivéssemos um parâmetro para f, poderíamos ignorar o nome na declaração, usando apenas o tipo, como em:

int f(int);

Da mesma forma, ao declarar S(), dentro dos parênteses na declaração S s(S()) faz com que o compilador assuma que esse S() seja uma declaração de um ponteiro para uma função sem um nome. De forma mais explícita, o compilador deveria aceitar apenas algo assim:

S s(S (*)());

Mas, ao omitir a notação de ponteiro o compilador continua entendendo que a declaração S() ainda é um parâmetro de s que é um ponteiro para uma função!

Ao compilar o programinha, obtemos isso:

$ g++ -c -o test.o test.cc
test.cc: In function ‘int main()’:
test.cc:19:18: error: request for member ‘x’ in ‘s’, which is of non-class type ‘S(S (*)())’
   std::cout << s.x << '\n';
                  ^

O que o compilador tá te dizendo é: “s não é um objeto, seu trouxa, porque ‘S(S(*)())’ também não é!”.

O problema todo está no fato de que C++ aceita a abreviação de “(void)” como “()” nas declarações de funções (e seus protótipos) e, daí, cria uma ambiguidade do que é uma declaração de função e o que é uma chamada implícita a um construtor default, num objeto anônimo!

Esse comportamento do parser do compilador C++ chama-se de “the most vexing parse ou “a análise mais irritante (ou vergonhosa)”… Isso faz parte da especificação de C++.

Por esse motivo, mesmo em C++, prefiro declarar explicitamente os tipos void em funções que não tomam parâmetros e evitar a instanciação implícita de objetos anônimos com chamadas a construtores default. Repare como declarei main, usando explicitamente o tipo void no parâmetro…

Outro conselho… Esqueça essa história que C++ é C com classes… Não é! C e C++ são linguagens com similaridades, mas não são a mesma coisa!

Anúncios