Namespaces anônimos / anônimos versus funções estáticas

Um recurso do C ++ é a capacidade de criar namespaces anônimos (anônimos), assim:

namespace { int cannotAccessOutsideThisFile() { ... } } // namespace 

Você poderia pensar que tal recurso seria inútil – já que você não pode especificar o nome do namespace, é impossível acessar qualquer coisa dentro dele de fora. Mas esses namespaces sem nome são acessíveis dentro do arquivo em que são criados, como se você tivesse uma cláusula de uso implícita para eles.

Minha pergunta é: por que ou quando isso seria preferível ao uso de funções estáticas? Ou são essencialmente duas maneiras de fazer exatamente a mesma coisa?

O padrão C ++ lê na seção 7.3.1.1 namespaces sem nome, parágrafo 2:

O uso da palavra-chave estática é reprovado ao declarar objects em um escopo de namespace, o namespace sem nome fornece uma alternativa superior.

Estático só se aplica a nomes de objects, funções e uniões anônimas, não a declarações de tipo.

Editar:

A decisão de descartar esse uso da palavra-chave estática (afetar a visibilidade de uma declaração de variável em uma unidade de conversão) foi revertida ( ref ). Neste caso, usar um espaço de nomes estático ou sem nome volta a ser essencialmente duas maneiras de fazer exatamente a mesma coisa. Para mais discussão, consulte esta questão.

Namespaces sem nome ainda têm a vantagem de permitir que você defina tipos locais de unidade de tradução. Por favor, veja esta questão para mais detalhes.

O crédito vai para Mike Percy por trazer isso à minha atenção.

Colocar methods em um namespace anônimo impede que você acidentalmente viole a regra de definição única , permitindo que você nunca se preocupe em nomear seus methods auxiliares da mesma forma que em outro método que você possa vincular.

E, como apontado por luke, os namespaces anônimos são preferidos pelo padrão em relação aos membros estáticos.

Há um caso extremo em que a estática tem um efeito surpreendente (pelo menos foi para mim). O C ++ 03 Standard afirma em 14.6.4.2/1:

Para uma chamada de function que depende de um parâmetro de modelo, se o nome da function for um id não qualificado, mas não um id de modelo , as funções candidatas serão encontradas usando as regras de pesquisa usuais (3.4.1, 3.4.2), exceto que:

  • Para a parte da pesquisa que utiliza consulta de nome não qualificada (3.4.1), somente as declarações de function com binding externa do contexto de definição de modelo são encontradas.
  • Para a parte da pesquisa que usa namespaces associados (3.4.2), somente as declarações de function com binding externa encontradas no contexto de definição de modelo ou no contexto de instanciação de modelo são encontradas.

O código abaixo irá chamar foo(void*) e não foo(S const &) como você poderia esperar.

 template  int b1 (T const & t) { foo(t); } namespace NS { namespace { struct S { public: operator void * () const; }; void foo (void*); static void foo (S const &); // Not considered 14.6.4.2(b1) } } void b2() { NS::S s; b1 (s); } 

Em si, isso provavelmente não é um grande negócio, mas destaca que, para um compilador C ++ totalmente compatível (ou seja, um com suporte para export ), a palavra-chave static ainda terá uma funcionalidade que não está disponível de nenhuma outra forma.

 // bar.h export template  int b1 (T const & t); // bar.cc #include "bar.h" template  int b1 (T const & t) { foo(t); } // foo.cc #include "bar.h" namespace NS { namespace { struct S { }; void foo (S const & s); // Will be found by different TU 'bar.cc' } } void b2() { NS::S s; b1 (s); } 

A única maneira de garantir que a function em nosso namespace sem nome não seja encontrada em modelos usando ADL é torná-la static .

Atualização para o C ++ moderno

A partir de C ++ ’11, os membros de um namespace sem nome possuem uma vinculação interna implicitamente (3.5 / 4):

Um namespace sem nome ou um namespace declarado direta ou indiretamente em um namespace sem nome possui uma binding interna.

Mas, ao mesmo tempo, 14.6.4.2/1 foi atualizado para remover a menção de linkage (isto retirado de C ++ ’14):

Para uma chamada de function em que a expressão de postfix é um nome dependente, as funções candidatas são encontradas usando as regras de pesquisa usuais (3.4.1, 3.4.2), exceto que:

  • Para a parte da pesquisa que utiliza consulta de nome não qualificada (3.4.1), somente as declarações de function do contexto de definição de modelo são encontradas.

  • Para a parte da pesquisa que usa namespaces associados (3.4.2), somente as declarações de function encontradas no contexto de definição de modelo ou no contexto de instanciação de modelo são encontradas.

O resultado é que essa diferença específica entre membros estáticos e não nomeados do espaço para nome não existe mais.

Recentemente, comecei a replace as palavras-chave estáticas por namespaces anônimos no meu código, mas corri imediatamente para um problema em que as variables ​​no namespace não estavam mais disponíveis para inspeção no meu depurador. Eu estava usando o VC60, então não sei se isso não é um problema com outros depuradores. Minha solução foi definir um namespace ‘module’, onde eu dei o nome do meu arquivo cpp.

Por exemplo, no meu arquivo XmlUtil.cpp, eu defino um namespace XmlUtil_I {…} para todas as variables ​​e funções do meu módulo. Dessa forma, posso aplicar a qualificação XmlUtil_I :: no depurador para acessar as variables. Nesse caso, o ‘_I’ o distingue de um namespace público, como o XmlUtil, que talvez eu queira usar em outro lugar.

Suponho que uma desvantagem potencial dessa abordagem em comparação a uma abordagem realmente anônima é que alguém poderia violar o escopo estático desejado usando o qualificador de namespace em outros módulos. Eu não sei se isso é uma grande preocupação.

O uso de palavras-chave estáticas para essa finalidade é obsoleto pelo padrão C ++ 98. O problema com a estática é que ela não se aplica à definição de tipo. Também é uma palavra-chave sobrecarregada usada de diferentes maneiras em diferentes contextos, de modo que namespaces sem nome simplificam um pouco as coisas.

A partir da experiência, apenas observarei que, embora seja a maneira C ++ de colocar funções anteriormente estáticas no namespace anônimo, os compiladores mais antigos podem, às vezes, ter problemas com isso. Eu atualmente trabalho com alguns compiladores para nossas plataformas de destino, e o compilador mais moderno do Linux é bom em colocar funções no namespace anônimo.

Mas um compilador mais antigo em execução no Solaris, ao qual iremos até uma versão futura não especificada, às vezes o aceitará e, outras vezes, sinalizará como um erro. O erro não é o que me preocupa, é o que ele pode estar fazendo quando o aceita . Então, até que fiquemos modernos em toda a linha, ainda estamos usando funções estáticas (geralmente com escopo de class) onde preferiríamos o namespace anônimo.

Além disso, se alguém usa uma palavra-chave estática em uma variável como este exemplo:

 namespace { static int flag; } 

Não seria visto no arquivo de mapeamento

Tendo aprendido sobre esse recurso apenas agora enquanto leio sua pergunta, posso apenas especular. Isso parece fornecer várias vantagens sobre uma variável estática no nível do arquivo:

  • Os namespaces anônimos podem ser nesteds uns dentro dos outros, fornecendo vários níveis de proteção dos quais os símbolos não podem escaping.
  • Vários namespaces anônimos podem ser colocados no mesmo arquivo de origem, criando, na verdade, diferentes escopos de nível estático no mesmo arquivo.

Eu estaria interessado em saber se alguém usou namespaces anônimos em código real.

Uma diferença específica do compilador entre namespaces anônimos e funções estáticas pode ser vista compilando o código a seguir.

 #include  namespace { void unreferenced() { std::cout < < "Unreferenced"; } void referenced() { std::cout << "Referenced"; } } static void static_unreferenced() { std::cout << "Unreferenced"; } static void static_referenced() { std::cout << "Referenced"; } int main() { referenced(); static_referenced(); return 0; } 

Compilando este código com o VS 2017 (especificando o sinalizador de alerta de nível 4 / W4 para ativar o aviso C4505: a function local não referenciada foi removida ) e o gcc 4.9 com a function -Wunused ou o sinalizador -Wall mostra que o VS 2017 só produzirá um aviso para a function estática não utilizada. O gcc 4.9 e superior, assim como o clang 3.3 e superior, produzirão avisos para a function não referenciada no namespace e também um aviso para a function estática não usada.

Demonstração ao vivo do gcc 4.9 e do MSVC 2017

Pessoalmente eu prefiro funções estáticas sobre namespaces sem nome pelas seguintes razões:

  • É óbvio e claro, apenas a partir da definição de function, que ela é privada para a unidade de tradução em que é compilada. Com namespace sem nome, você pode precisar rolar e pesquisar para ver se uma function está em um namespace.

  • funções em namespaces podem ser tratadas como externas por alguns compiladores (mais antigos). No VS2017 eles ainda são externos. Por essa razão, mesmo que uma function esteja em namespace sem nome, talvez você ainda queira marcá-las como estáticas.

  • funções estáticas se comportam de maneira muito semelhante em C ou C ++, enquanto namespaces sem nome são obviamente apenas C ++. namespaces sem nome também adicionam nível extra se recuo e eu não gosto disso 🙂

Então, fico feliz em ver que o uso de estática para funções não está mais sendo preterido .