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


508

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

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

Você pensaria que esse recurso seria inútil - já que você não pode especificar o nome do espaço para nome, é impossível acessar qualquer coisa nele de fora. Mas esses espaços de nome sem nome são acessíveis no arquivo em que são criados, como se você tivesse uma cláusula implícita de uso.

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?


13
No C ++ 11, o uso staticdesse contexto não foi preterido ; embora o espaço para nome sem nome seja uma alternativa superiorstatic , há casos em que falha quando se statictrata de resgate .
legends2k

Respostas:


332

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 é descontinuado ao declarar objetos em um escopo de espaço para nome, o espaço para nome sem nome fornece uma alternativa superior.

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

Editar:

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

Os espaços para nome sem nome ainda têm a vantagem de permitir definir tipos de unidades de tradução locais. Por favor, veja esta pergunta para mais detalhes.

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


39
O Head Geek pergunta sobre a palavra-chave estática usada apenas nas funções. A palavra-chave estática aplicada à entidade declarada no escopo do espaço para nome especifica sua ligação interna. A entidade declarada no espaço para nome anônimo possui ligação externa (C ++ / 3.5), no entanto, é garantido que ele vive em escopo nomeado exclusivamente. Esse anonimato do espaço para nome sem nome oculta efetivamente sua declaração, tornando-a acessível apenas dentro de uma unidade de tradução. O último funciona efetivamente da mesma maneira que a palavra-chave estática.
23610 mloskot

5
qual é a desvantagem da ligação externa? Isso poderia afetar o inlining?
Alex

17
Os membros do comitê de design do C ++ que disseram que a palavra-chave estática está obsoleta provavelmente nunca trabalharam com um código C enorme em um grande sistema do mundo real ... (Você vê imediatamente uma palavra-chave estática, mas não o espaço para nome anônimo, se ela contiver muitas declarações com comentários grandes blocos).
Calmarius

23
Como esta resposta aparece no Google como o principal resultado do "espaço para nome anônimo c ++", observe que o uso de estática não é mais descontinuado. Consulte stackoverflow.com/questions/4726570/… e open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 para obter mais informações.
Michael Percy

2
@ErikAronesty Isso parece errado. Você tem um exemplo reproduzível? A partir do C ++ 11 - e mesmo antes disso em alguns compiladores - namespaces sem nome implicitamente têm ligação interna, portanto não deve haver diferença. Quaisquer problemas que possam ter surgido anteriormente de palavras incorretas foram resolvidos, tornando isso um requisito no C ++ 11.
underscore_d

73

A colocação de métodos em um espaço de nome anônimo evita que você viole acidentalmente a Regra de Uma Definição , permitindo que você nunca se preocupe em nomear seus métodos auxiliares da mesma forma que outros métodos aos quais você pode vincular.

E, como apontado por luke, os espaços para nome anônimos são preferidos pelo padrão sobre os membros estáticos.


2
Eu estava me referindo a funções autônomas estáticas (ou seja, funções com escopo de arquivo), não a funções membro estáticas. Funções autônomas estáticas são praticamente as mesmas de um espaço para nome sem nome, portanto, a questão.
Head Geek

2
Ah; bem, o ODR ainda se aplica. Editado para remover o parágrafo.
hazzen 30/09/08

como recebo, o ODR para uma função estática não funciona quando definido no cabeçalho e esse cabeçalho é incluído em mais de uma unidade de tradução, certo? nesse caso, você recebe várias cópias da mesma função
Andriy Tylychko

@ Andy T: Você realmente não vê as "definições múltiplas" no caso do cabeçalho incluído. O pré-processador cuida disso. A menos que seja necessário estudar a saída que o pré-processador gerou, o que para mim parece bastante exótico e raro. Também existe uma boa prática para incluir "guardas" nos arquivos de cabeçalho, como: "#ifndef SOME_GUARD - #definir SOME_GUARD ...", que deve impedir o pré-processador de incluir o mesmo cabeçalho duas vezes.
Nikita Vorontsov

@NikitaVorontsov o guarda pode impedir a inclusão do mesmo cabeçalho na mesma unidade de tradução, no entanto, permite várias definições em diferentes unidades de tradução. Isso pode causar um erro de vinculador de "várias definições" na linha.
2828 Alex

37

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

Para uma chamada de função que depende de um parâmetro de modelo, se o nome da função 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 pelo seguinte:

  • Para a parte da pesquisa usando a pesquisa de nome não qualificado (3.4.1), são encontradas apenas declarações de função com ligação externa do contexto de definição do modelo.
  • Para a parte da pesquisa que usa namespaces associados (3.4.2), apenas as declarações de função com ligação externa encontradas no contexto de definição do modelo ou no contexto de instanciação do modelo são encontradas.

...

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

template <typename T>
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 é tão importante, mas destaca que, para um compilador C ++ totalmente compatível (ou seja, um com suporte para export), a staticpalavra - chave ainda terá uma funcionalidade que não está disponível de nenhuma outra maneira.

// bar.h
export template <typename T>
int b1 (T const & t);

// bar.cc
#include "bar.h"
template <typename T>
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 função em nosso namespace não nomeado não seja encontrada em modelos usando ADL é fazê-lo static.

Atualização para C ++ moderno

A partir do C ++ '11, os membros de um espaço para nome sem nome têm vínculo interno implicitamente (3.5 / 4):

Um espaço para nome sem nome ou um espaço para nome declarado direta ou indiretamente em um espaço para nome sem nome tem ligação interna.

Mas, ao mesmo tempo, 14.6.4.2/1 foi atualizado para remover a menção de ligação (retirada do C ++ '14):

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

  • Para a parte da pesquisa usando a pesquisa de nome não qualificado (3.4.1), apenas as declarações de função do contexto de definição do modelo são encontradas.

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

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


3
A palavra-chave de exportação não deveria estar morta a frio? As únicas compiladores apoio "exportação" são experimentais, ea menos surpresas, "exportar" não vai mesmo ser implementado em outros por causa de efeitos colaterais inesperados (além de não fazer foi que era esperado para)
paercebal

2
Veja o artigo de Herb Sutter na subjet: gotw.ca/publications/mill23-x.htm
paercebal

3
O front-end do Edison Design Group (EDG) é tudo menos experimental. É quase certamente a implementação C ++ em conformidade mais padrão do mundo. O compilador Intel C ++ usa EDG.
Richard Corden

1
Qual recurso C ++ não tem 'efeitos colaterais inesperados'? No caso da exportação, é possível encontrar uma função de espaço para nome sem nome de uma TU diferente - é a mesma que se você incluísse a definição do modelo diretamente. Seria mais surpreendente se não fosse assim!
Richard Corden

Eu acho que você tem um erro de digitação lá - para NS::Strabalhar, não Sprecisa não estar lá dentro namespace {}?
Eric

12

Recentemente, comecei a substituir palavras-chave estáticas por namespaces anônimos no meu código, mas imediatamente tive um problema em que as variáveis ​​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 é um problema com outros depuradores. Minha solução alternativa foi definir um espaço para nome 'module', no qual dei o nome do meu arquivo cpp.

Por exemplo, no meu arquivo XmlUtil.cpp, defino um espaço XmlUtil_I { ... }para nome para todas as minhas variáveis ​​e funções do módulo. Dessa forma, posso aplicar a XmlUtil_I::qualificação no depurador para acessar as variáveis. Nesse caso, o _Idistingue de um espaço para nome público como o XmlUtilque eu posso querer usar em outro lugar.

Suponho que uma desvantagem potencial dessa abordagem, em comparação com uma abordagem verdadeiramente anônima, é que alguém pode violar o escopo estático desejado usando o qualificador de espaço para nome em outros módulos. Não sei se isso é uma grande preocupação.


7
Também fiz isso, mas com #if DEBUG namespace BlahBlah_private { #else namespace { #endif, portanto, o "namespace do módulo" está presente apenas nas compilações de depuração e o namespace anônimo verdadeiro é usado de outra forma. Seria bom se os depuradores dessem uma boa maneira de lidar com isso. O oxigênio também fica confuso com ele.
Kristopher Johnson

4
namespace sem nome não é realmente um substituto viável para estático. static significa "realmente isso nunca fica vinculado fora da UT". namespace sem nome significa "ainda é exportado, como um nome aleatório, caso seja chamado de uma classe pai que esteja fora da TU" ...
Erik Aronesty

7

O uso da palavra-chave estática para esse fim foi descontinuado 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 maneiras diferentes em contextos diferentes, para que os namespaces sem nome simplifiquem um pouco as coisas.


1
Se você deseja usar um tipo apenas em uma única unidade de tradução, declare-o dentro do arquivo .cpp. Não estará acessível a partir de outras unidades de tradução.
Calmarius

4
Você pensaria, não é? Mas se outra unidade de tradução (= arquivo-cpp) no mesmo aplicativo declarar um tipo com o mesmo nome, você terá problemas bastante difíceis de depurar :-). Por exemplo, você pode acabar com situações em que a vtable de um dos tipos é usada ao chamar métodos por outro.
avl_sweden

1
Não está mais obsoleto. E defs de tipo não são exportados, então isso não faz sentido. estática é útil para funções independentes e vars globais. namespaces não nomeados são úteis para classes.
Erik Aronesty

6

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

Mas um compilador mais antigo em execução no Solaris, com o qual estamos casados ​​até uma versão futura não especificada, às vezes o aceita e outras vezes o sinaliza como um erro. O erro não é o que me preocupa, é o que ele pode estar fazendo quando o aceita . Então, até que nos tornemos modernos em geral, ainda estamos usando funções estáticas (geralmente com escopo de classe) onde preferimos o espaço para nome anônimo.


3

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

namespace {
   static int flag;
}

Não seria visto no arquivo de mapeamento


7
Então você não precisa de um namespace anônimo.
Calmarius

2

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 <iostream>

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 aviso de nível 4 / W4 para ativar o aviso C4505: a função local não referenciada foi removida ) e o gcc 4.9 com o sinalizador -Wunused-function ou -Wall mostra que o VS 2017 produzirá apenas um aviso para a função estática não utilizada. O gcc 4.9 e superior, assim como o clang 3.3 e superior, produzirão avisos para a função não referenciada no espaço para nome e também um aviso para a função estática não utilizada.

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


2

Pessoalmente, prefiro funções estáticas do que os namespaces sem nome pelos seguintes motivos:

  • É óbvio e claro, apenas pela definição da função, que é privado para a unidade de tradução onde é compilado. Com espaço para nome sem nome, você pode precisar rolar e procurar para ver se uma função está em um espaço para nome.

  • Funções em espaços para nome podem ser tratadas como externas por alguns compiladores (mais antigos). No VS2017 eles ainda são externos. Por esse motivo, mesmo que uma função esteja no namespace sem nome, você ainda pode querer marcá-las como estáticas.

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

Portanto, fico feliz em ver que o uso de static para funções não é mais preterido .


As funções nos espaços para nome anônimos devem ter ligação externa. Eles são apenas mutilados para torná-los únicos. Somente a staticpalavra - chave aplica o vínculo local a uma função. Além disso, certamente apenas um lunático delirante acrescentaria recuo para namespaces?
usar o seguinte código

0

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

  • Os namespaces anônimos podem ser aninhados um no outro, fornecendo vários níveis de proteção dos quais os símbolos não podem escapar.
  • Vários namespaces anônimos podem ser colocados no mesmo arquivo de origem, criando com efeito 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.


4
Boas especulações, mas erradas. O escopo desses espaços para nome é de todo o arquivo.
319 Konrad Rudolph

Não é exatamente verdade, se você definir um espaço para nome anônimo dentro de outro espaço para nome, ele ainda terá apenas o arquivo inteiro e poderá ser visto apenas como dentro desse espaço para nome. Tente.
Greg Rogers

Eu posso estar errado, mas acho que não, não é de todo o arquivo: é acessível apenas ao código após o espaço para nome anônimo. Isso é uma coisa sutil e, geralmente, eu não gostaria de poluir uma fonte com vários namespaces anônimos ... Ainda assim, isso pode ter usos.
22468 paercebal

0

A diferença é o nome do identificador desconfigurado ( _ZN12_GLOBAL__N_11bEvs _ZL1b, o que realmente não importa, mas os dois são montados em símbolos locais na tabela de símbolos (ausência de .globaldiretiva asm).

#include<iostream>
namespace {
   int a = 3;
}

static int b = 4;
int c = 5;

int main (){
    std::cout << a << b << c;
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4
_ZL1b:
        .long   4
        .globl  c
        .align 4
        .type   c, @object
        .size   c, 4
c:
        .long   5
        .text

Quanto a um espaço de nome anônimo aninhado:

namespace {
   namespace {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_112_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

Todos os namespaces anônimos de 1º nível na unidade de tradução são combinados entre si, Todos os namespaces anônimos anônimos de 2º nível na unidade de tradução são combinados entre si

Você também pode ter um espaço para nome aninhado (em linha) em um espaço para nome anônimo

namespace {
   namespace A {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11A1aE, @object
        .size   _ZN12_GLOBAL__N_11A1aE, 4
_ZN12_GLOBAL__N_11A1aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

which for the record demangles as:
        .data
        .align 4
        .type   (anonymous namespace)::A::a, @object
        .size   (anonymous namespace)::A::a, 4
(anonymous namespace)::A::a:
        .long   3
        .align 4
        .type   b, @object
        .size   b, 4

Você também pode ter namespaces anônimos inline, mas até onde eu sei, inlineum namespace anônimo tem efeito 0

inline namespace {
   inline namespace {
       int a = 3;
    }
}

_ZL1b: _Zsignifica que este é um identificador mutilado. Lsignifica que é um símbolo local através de static. 1é o comprimento do identificador be, em seguida, o identificadorb

_ZN12_GLOBAL__N_11aE _Zsignifica que este é um identificador mutilado. Nsignifica que este é um espaço para nome, 12é o tamanho do nome do espaço para nome anônimo _GLOBAL__N_1, o nome do espaço para nome anônimo _GLOBAL__N_1, 1o comprimento do identificador a, ao identificador aeE fecha o identificador que reside em um espaço para nome.

_ZN12_GLOBAL__N_11A1aE é o mesmo que acima, exceto que há outro nível de espaço para nome nele 1A

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.