Os espaços para nome em linha são um recurso de versão de biblioteca semelhante ao de versão de símbolo , mas implementado puramente no nível C ++ 11 (por exemplo, plataforma cruzada) em vez de ser um recurso de um formato executável binário específico (por exemplo, específico da plataforma).
É um mecanismo pelo qual o autor de uma biblioteca pode fazer com que um espaço de nome aninhado pareça e atue como se todas as suas declarações estivessem no espaço de nome ao redor (os espaços de nome em linha podem ser aninhados, para que nomes "mais aninhados" cheguem até o primeiro nome não -inclua o espaço para nome e olhe e aja como se suas declarações também estivessem em qualquer um dos espaços para nome).
Como exemplo, considere a implementação de STL de vector
. Se tivéssemos espaços para nome embutidos desde o início do C ++, no C ++ 98 o cabeçalho <vector>
poderia ter se parecido com o seguinte:
namespace std {
#if __cplusplus < 1997L // pre-standard C++
inline
#endif
namespace pre_cxx_1997 {
template <class T> __vector_impl; // implementation class
template <class T> // e.g. w/o allocator argument
class vector : __vector_impl<T> { // private inheritance
// ...
};
}
#if __cplusplus >= 1997L // C++98/03 or later
// (ifdef'ed out b/c it probably uses new language
// features that a pre-C++98 compiler would choke on)
# if __cplusplus == 1997L // C++98/03
inline
# endif
namespace cxx_1997 {
// std::vector now has an allocator argument
template <class T, class Alloc=std::allocator<T> >
class vector : pre_cxx_1997::__vector_impl<T> { // the old impl is still good
// ...
};
// and vector<bool> is special:
template <class Alloc=std::allocator<bool> >
class vector<bool> {
// ...
};
};
#endif // C++98/03 or later
} // namespace std
Dependendo do valor de __cplusplus
, uma ou outra vector
implementação é escolhida. Se sua base de código foi escrita antes de C ++ 98 vezes e você descobrir que a versão C ++ 98 vector
está causando problemas ao atualizar seu compilador, "tudo" que você precisa fazer é encontrar as referências std::vector
em sua base de código e substitua-os por std::pre_cxx_1997::vector
.
Venha o próximo padrão e o fornecedor STL apenas repita o procedimento novamente, introduzindo um novo espaço para nome std::vector
com emplace_back
suporte (que requer C ++ 11) e destacando esse iff __cplusplus == 201103L
.
OK, então por que preciso de um novo recurso de idioma para isso? Já posso fazer o seguinte para ter o mesmo efeito, não?
namespace std {
namespace pre_cxx_1997 {
// ...
}
#if __cplusplus < 1997L // pre-standard C++
using namespace pre_cxx_1997;
#endif
#if __cplusplus >= 1997L // C++98/03 or later
// (ifdef'ed out b/c it probably uses new language
// features that a pre-C++98 compiler would choke on)
namespace cxx_1997 {
// ...
};
# if __cplusplus == 1997L // C++98/03
using namespace cxx_1997;
# endif
#endif // C++98/03 or later
} // namespace std
Dependendo do valor de __cplusplus
, recebo uma ou outra das implementações.
E você estaria quase correto.
Considere o seguinte código de usuário válido do C ++ 98 (foi permitido especializar totalmente modelos que já estão no namespace std
do C ++ 98):
// I don't trust my STL vendor to do this optimisation, so force these
// specializations myself:
namespace std {
template <>
class vector<MyType> : my_special_vector<MyType> {
// ...
};
template <>
class vector<MyOtherType> : my_special_vector<MyOtherType> {
// ...
};
// ...etc...
} // namespace std
Esse é um código perfeitamente válido, no qual o usuário fornece sua própria implementação de um vetor para um conjunto de tipos em que ela aparentemente conhece uma implementação mais eficiente do que a encontrada na (sua cópia) da STL.
Porém : ao especializar um modelo, é necessário fazê-lo no espaço para nome em que foi declarado. O Padrão diz que vector
é declarado no espaço para nomestd
para , e é aí que o usuário espera justamente especializar o tipo.
Esse código funciona com um espaço para nome sem versão std
ou com o recurso de espaço para nome embutido no C ++ 11, mas não com o truque de versão usado using namespace <nested>
, porque expõe os detalhes da implementação que o verdadeiro espaço para nome no qual vector
foi definido não foi std
diretamente.
Existem outros buracos pelos quais você pode detectar o espaço para nome aninhado (consulte os comentários abaixo), mas os espaços para nome embutidos conectam todos eles. E é só isso. Imensamente útil para o futuro, mas o AFAIK the Standard não prescreve nomes de namespace em linha para sua própria biblioteca padrão (eu adoraria provar que está errado nisso), portanto, ele só pode ser usado para bibliotecas de terceiros, não o próprio padrão (a menos que os fornecedores do compilador concordem com um esquema de nomenclatura).
using namespace V99;
que não funciona no exemplo de Stroustrup.std::cxx_11
. Nem todo compilador sempre implementará todas as versões antigas das bibliotecas padrão, embora seja tentador, no momento, pensar que seria muito pouco fardo exigir que as implementações existentes deixem as antigas quando adicionam as novas, pois na verdade todas elas são assim mesmo. Suponho que o que o padrão poderia ter sido útil o torne opcional, mas com um nome padrão, se presente.using namespace A
em um espaço para nome B, os nomes no espaço para nome B ocultam nomes no espaço para nome A, se você procurarB::name
- não com espaços para nome embutidos).ifdef
s para a implementação completa do vetor? Todas as implementações seria em um espaço de nomes, mas apenas um deles será definido após o pré-processamentousing
palavra-chave).http://www.stroustrup.com/C++11FAQ.html#inline-namespace (um documento escrito e mantido por Bjarne Stroustrup, que você acha que deveria estar ciente da maioria das motivações para a maioria dos recursos do C ++ 11. )
De acordo com isso, é para permitir versões para compatibilidade com versões anteriores. Você define vários espaços para nome internos e cria o mais recente
inline
. De qualquer forma, o padrão para pessoas que não se importam com o controle de versão. Suponho que a mais recente possa ser uma versão futura ou de ponta que ainda não seja o padrão.O exemplo dado é:
Não percebo imediatamente por que você não
using namespace V99;
insere um espaço para nomeMine
, mas não preciso entender completamente o caso de uso para que a palavra de Bjarne seja motivada pelo comitê.fonte
f(1)
versão seria chamada a partir doV99
namespace embutido ?using namespace Mine;
e oMine
espaço para nome contém tudo, desde o espaço para nome embutidoMine::V99
.inline
do arquivoV99.h
na versão que incluiV100.h
. Você também modificaMine.h
ao mesmo tempo, é claro, para adicionar uma inclusão extra.Mine.h
faz parte da biblioteca, não faz parte do código do cliente.V100.h
, estão instalando uma biblioteca chamada "Mine". Existem 3 arquivos de cabeçalho na versão 99 de "Mine" -Mine.h
,V98.h
eV99.h
. Existem 4 arquivos de cabeçalho na versão 100 de "Mine" -Mine.h
,V98.h
,V99.h
eV100.h
. A organização dos arquivos de cabeçalho é um detalhe de implementação que é irrelevante para os usuários. Se eles descobrirem algum problema de compatibilidade, o que significa que precisam usar especificamenteMine::V98::f
de parte ou de todo o código, eles poderão misturar chamadasMine::V98::f
do código antigo com chamadasMine::f
no código recém-escrito.Mine
, em vez de precisar se especializar emMine::V99
ouMine::V98
.Além de todas as outras respostas.
O espaço para nome embutido pode ser usado para codificar informações da ABI ou Versão das funções nos símbolos. É por esse motivo que eles são usados para fornecer compatibilidade ABI com versões anteriores. Os espaços para nome em linha permitem injetar informações no nome desconfigurado (ABI) sem alterar a API, pois afetam apenas o nome do símbolo do vinculador.
Considere este exemplo:
Suponha que você escreva uma função
Foo
que faça referência a um objeto saybar
e não retorne nada.Diga em main.cpp
Se você verificar o nome do seu símbolo para esse arquivo depois de compilá-lo em um objeto.
Agora, pode ser que
bar
seja definido como:Dependendo do tipo de compilação,
bar
pode se referir a dois tipos / layouts diferentes com os mesmos símbolos do vinculador.Para evitar esse comportamento, envolvemos nossa estrutura
bar
em um espaço de nome embutido, onde, dependendo do tipo de compilação, o símbolo do vinculadorbar
será diferente.Então, poderíamos escrever:
Agora, se você olhar para o arquivo de objeto de cada objeto, crie um usando release e outro com o sinalizador de depuração. Você descobrirá que os símbolos do vinculador também incluem o nome do espaço para nome embutido. Nesse caso
Observe a presença
rel
edbg
nos nomes dos símbolos.Agora, se você tentar vincular a depuração ao modo de liberação ou vice-versa, receberá um erro do vinculador como contrário ao erro de tempo de execução.
fonte
Na verdade, descobri outro uso para namespaces embutidos.
Com o Qt , você obtém alguns recursos extras e agradáveis
Q_ENUM_NS
, que, por sua vez, exigem que o espaço para nome anexo tenha um meta-objeto, que é declarado comQ_NAMESPACE
. No entanto,Q_ENUM_NS
para funcionar, é necessário que haja um correspondenteQ_NAMESPACE
no mesmo arquivo ⁽¹⁾. E só pode haver um, ou você recebe erros de definição duplicados. Isso significa efetivamente que todas as suas enumerações precisam estar no mesmo cabeçalho. Que nojo.Ou ... você pode usar espaços para nome embutidos. Ocultar enumerações em um
inline namespace
faz com que os meta-objetos tenham diferentes nomes desconfigurados, enquanto olhar para os usuários como o espaço para nome adicional não existe⁽²⁾.Portanto, eles são úteis para dividir coisas em vários sub-namespaces que se parecem com um namespace, se você precisar fazer isso por algum motivo. Obviamente, isso é semelhante à escrita
using namespace inner
no espaço para nome externo, mas sem a violação DRY de escrever o nome do espaço para nome interno duas vezes.Na verdade, é pior que isso; deve estar no mesmo conjunto de chaves.
A menos que você tente acessar o meta-objeto sem qualificá-lo completamente, mas o meta-objeto quase nunca é usado diretamente.
fonte
Portanto, para resumir os pontos principais,
using namespace v99
einline namespace
não eram os mesmos, o primeiro era uma solução alternativa para as bibliotecas de versões antes de uma palavra-chave dedicada (inline) ser introduzida no C ++ 11, que corrigia os problemas de usousing
e fornecia a mesma funcionalidade de versão. O uso deusing namespace
usado para causar problemas com o ADL (embora o ADL agora pareça seguir asusing
diretrizes) e a especialização fora de linha de uma classe / função da biblioteca etc. pelo usuário não funcionariam se fossem feitas fora do namespace verdadeiro (cujo nome o o usuário não saberia e não deveria saber, ou seja, o usuário teria que usar B :: abi_v2 :: em vez de apenas B :: para a especialização resolver).Isso mostrará um aviso de análise estática
first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]
. Mas se você criar o espaço para nome A embutido, o compilador resolverá corretamente a especialização. Embora, com as extensões do C ++ 11, o problema desapareça.Definições fora de linha não resolvem ao usar
using
; eles precisam ser declarados em um bloco de namespace de extensão aninhada / não aninhada (o que significa que o usuário precisa conhecer a versão ABI novamente, se por qualquer motivo, tiver permissão para fornecer sua própria implementação de uma função).O problema desaparece ao criar o B inline.
Os outros
inline
namespaces de funcionalidade possuem permitem que o gravador da biblioteca forneça uma atualização transparente para a biblioteca 1) sem forçar o usuário a refatorar o código com o novo nome do namespace e 2) evitando falta de verbosidade e 3) fornecendo abstração de detalhes irrelevantes da API, enquanto 4) fornecer o mesmo diagnóstico e comportamento benéfico do vinculador que o uso de um espaço para nome não embutido forneceria. Digamos que você esteja usando uma biblioteca:Ele permite que o usuário ligue
library::foo
sem precisar saber ou incluir a versão ABI na documentação, que parece mais limpa. Usarlibrary::abiverison129389123::foo
pareceria sujo.Quando uma atualização é feita para
foo
, ou seja, adicionar um novo membro à classe, ela não afeta os programas existentes no nível da API, porque eles ainda não estão usando o membro E a alteração no nome do namespace embutido não altera nada no nível da API. porquelibrary::foo
ainda vai funcionar.No entanto, para programas vinculados a ele, porque o nome do espaço para nome embutido é dividido em nomes de símbolo como um espaço para nome regular, a alteração não será transparente para o vinculador. Portanto, se o aplicativo não for recompilado, mas estiver vinculado a uma nova versão da biblioteca, ele apresentará um
abi_v1
erro de erro não encontrado, em vez de vincular e causar um erro lógico misterioso no tempo de execução devido à incompatibilidade ABI. A adição de um novo membro causará compatibilidade com a ABI devido à alteração na definição de tipo, mesmo que não afete o programa no momento da compilação (nível da API).Neste cenário:
Assim como o uso de dois namespaces não embutidos, ele permite que uma nova versão da biblioteca seja vinculada sem a necessidade de recompilar o aplicativo, porque
abi_v1
será confundida com um dos símbolos globais e usará a definição de tipo correta (antiga). No entanto, recompilar o aplicativo faria com que as referências fossem resolvidaslibrary::abi_v2
.Usar
using namespace
é menos funcional do que usarinline
(pois as definições fora de linha não resolvem), mas oferece as mesmas 4 vantagens acima. Mas a verdadeira questão é: por que continuar usando uma solução alternativa quando agora existe uma palavra-chave dedicada para fazer isso? É uma prática melhor, menos detalhada (precisa alterar uma linha de código em vez de 2) e torna clara a intenção.fonte