“Usando namespace” em cabeçalhos c ++

119

Em todos os nossos cursos C ++, todos os professores sempre colocam using namespace std;logo após os #includes em seus .harquivos. Isso me parece perigoso desde então, ao incluir aquele cabeçalho em outro programa, irei obter o namespace importado para o meu programa, talvez sem perceber, pretender ou desejar (a inclusão do cabeçalho pode ser profundamente aninhada).

Portanto, minha pergunta é dupla: Estou certo de que using namespacenão deve ser usado em arquivos de cabeçalho e / ou há alguma maneira de desfazê-lo, algo como:

//header.h
using namespace std {
.
.
.
}

Mais uma pergunta na mesma linha: um arquivo de cabeçalho deve conter #includetodos os cabeçalhos de que seu .cpparquivo correspondente precisa, apenas aqueles que são necessários para as definições de cabeçalho e deixar o resto do .cpparquivo #include, ou nenhum e declarar tudo o que precisa extern?
O raciocínio por trás da pergunta é o mesmo acima: Não quero surpresas ao incluir .harquivos.

Além disso, se eu estiver certo, isso é um erro comum? Quero dizer, na programação do mundo real e em projetos "reais" por aí.

Obrigado.

Baruch
fonte
3
como observação lateral, se você obtiver conflitos de nomes devido a using namespacedeclarações, poderá usar o nome totalmente qualificado para resolver o problema.
Marius Bancila

Respostas:

115

Definitivamente, você NÃO deve usar using namespaceem cabeçalhos precisamente pelo motivo que disse, que pode alterar inesperadamente o significado do código em qualquer outro arquivo que inclua esse cabeçalho. Não há como desfazer um, o using namespaceque é outro motivo pelo qual é tão perigoso. Normalmente, apenas uso grepou algo semelhante para ter certeza de que using namespacenão está sendo mencionado nos cabeçalhos, em vez de tentar algo mais complicado. Provavelmente verificadores de código estático sinalizam isso também.

O cabeçalho deve incluir apenas os cabeçalhos que ele precisa para compilar. Uma maneira fácil de impor isso é sempre incluir o cabeçalho do próprio arquivo de origem como a primeira coisa, antes de quaisquer outros cabeçalhos. Então, o arquivo de origem não será compilado se o cabeçalho não for independente. Em alguns casos, por exemplo, referindo-se a classes de detalhes de implementação em uma biblioteca, você pode usar declarações de encaminhamento em vez de #includeporque você tem controle total sobre a definição de tal classe declarada de encaminhamento.

Não tenho certeza se chamaria de comum, mas definitivamente aparece de vez em quando, geralmente escrito por novos programadores que não estão cientes das consequências negativas. Normalmente, apenas um pouco de educação sobre os riscos resolve todos os problemas, pois é relativamente simples de corrigir.

Mark B
fonte
2
somos livres para usar usingdeclarações em nossos .cpparquivos? os 3rdPartyLib::BigClassName<3rdPartyLib::AnotherBigName,3rdPartyLib::AnotherBigName>::Iterators são a morte na ponta dos dedos.
Christopher
1
e como devemos simplificar as templatefunções - que deveriam estar nos cabeçalhos? typedefs?
Christopher
1
@donlan, parece que você não obteve resposta por um bom tempo ... Sim, você pode usar usingdeclarações dentro de .cpparquivos sem muita preocupação porque o escopo será limitado apenas a esse arquivo, mas nunca faça isso antes de uma #includedeclaração. Quanto às funções de modelo definidas em cabeçalhos, infelizmente não conheço uma boa solução além de apenas escrever o namespace ... Talvez você pudesse colocar uma usingdeclaração dentro de um escopo separado { /* using statement in between brackets */ }, que pelo menos impediria de escapar do arquivo atual .
tjwrona1992
26

Item 59 nos "Padrões de codificação C ++: 101 regras, diretrizes e melhores práticas" de Sutter e Alexandrescu :

59. Não escreva usos de namespace em um arquivo de cabeçalho ou antes de um #include.

Os namespaces usingsão para sua conveniência, não para você infligir aos outros: Nunca escreva uma usingdeclaração ou usingdiretiva antes de uma #includediretiva.

Corolário: Em arquivos de cabeçalho, não escreva usingdiretivas ou usingdeclarações em nível de namespace ; em vez disso, qualifica explicitamente todos os nomes por namespace.

Um arquivo de cabeçalho é um convidado em um ou mais arquivos de origem. Um arquivo de cabeçalho que inclui usingdiretivas e declarações atrai seus amigos desordeiros também.

Uma using declaração traz um amigo. Uma using diretiva traz todos os amigos no namespace. O uso de seus professores using namespace std;é uma diretiva de uso.

Mais a sério, temos namespaces para evitar conflito de nomes. Um arquivo de cabeçalho destina-se a fornecer uma interface. A maioria dos cabeçalhos é independente de qual código pode incluí-los, agora ou no futuro. Adicionar usinginstruções para conveniência interna no cabeçalho impõe esses nomes convenientes a todos os clientes potenciais desse cabeçalho. Isso pode levar a conflito de nomes. E é simplesmente rude.

Andy Thomas
fonte
12

Você precisa ter cuidado ao incluir cabeçalhos dentro de cabeçalhos. Em grandes projetos, ele pode criar uma cadeia de dependências muito emaranhada que dispara reconstruções maiores / mais longas do que o realmente necessário. Confira este artigo e seu acompanhamento para aprender mais sobre a importância de uma boa estrutura física em projetos C ++.

Você só deve incluir cabeçalhos dentro de um cabeçalho quando absolutamente necessário (sempre que a definição completa de uma classe for necessária) e usar a declaração de encaminhamento sempre que puder (quando a classe necessária for um ponteiro ou uma referência).

Quanto aos namespaces, tendo a usar o escopo de namespace explícito em meus arquivos de cabeçalho e apenas coloco um using namespaceem meus arquivos cpp.

Mike O'Connor
fonte
1
como você agiliza a templatedeclaração de função? isso tem que ocorrer no cabeçalho, não?
Christopher
6

Verifique os padrões de codificação do Goddard Space Flight Center (para C e C ++). Isso acabou sendo um pouco mais difícil do que costumava ser - veja as respostas atualizadas para as perguntas do SO:

O padrão de codificação GSFC C ++ diz:

§3.3.7 Cada arquivo de cabeçalho deve conter #includeos arquivos de que precisa para compilar, em vez de forçar os usuários a usar #includeos arquivos necessários. #includesdeve ser limitado ao que o cabeçalho precisa; outro #includesdeve ser colocado no arquivo de origem.

A primeira das perguntas com referência cruzada agora inclui uma citação do padrão de codificação GSFC C e a justificativa, mas a substância acaba sendo a mesma.

Jonathan Leffler
fonte
5

Você está certo que o using namespacecabeçalho é perigoso. Não sei como desfazer isso. É fácil detectá-lo, porém, basta pesquisar using namespacenos arquivos de cabeçalho. Por esse último motivo, é incomum em projetos reais. Colegas de trabalho mais experientes logo reclamarão se alguém fizer algo parecido.

Em projetos reais, as pessoas tentam minimizar a quantidade de arquivos incluídos, porque quanto menos você incluir, mais rápido será a compilação. Isso economiza o tempo de todos. No entanto, se o arquivo de cabeçalho presume que algo deve ser incluído antes dele, ele deve incluí-lo. Caso contrário, torna os cabeçalhos não independentes.

Öö Tiib
fonte
4

Você está certo. E qualquer arquivo deve incluir apenas os cabeçalhos necessários para esse arquivo. Quanto a "fazer coisas erradas é comum em projetos do mundo real?" - Ai sim!


fonte
4

Como todas as coisas na programação, o pragmatismo deve vencer o dogmatismo, IMO.

Contanto que você tome a decisão em todo o projeto ("Nosso projeto usa STL extensivamente e não queremos ter que preceder tudo com std ::."), Não vejo problema nisso. Afinal, a única coisa que você está arriscando são as colisões de nomes, e com a onipresença da STL, é improvável que seja um problema.

Por outro lado, se foi uma decisão de um desenvolvedor em um único arquivo de cabeçalho (não privado), posso ver como isso geraria confusão entre a equipe e deveria ser evitado.

ijprest
fonte
4

Com relação a "Existe alguma maneira de desfazer [uma usingdeclaração]?"

Acho que é útil salientar que as usingdeclarações são afetadas pelo escopo.

#include <vector>

{   // begin a new scope with {
    using namespace std;
    vector myVector;  // std::vector is used
}   // end the scope with }

vector myOtherVector;   // error vector undefined
std::vector mySTDVector // no error std::vector is fully qualified

Então, efetivamente, sim. Ao limitar o âmbito da usingdeclaração, o seu efeito só perdura nesse âmbito; é 'desfeito' quando esse escopo termina.

Quando a usingdeclaração é declarada em um arquivo fora de qualquer outro escopo, ela possui o escopo do arquivo e afeta tudo naquele arquivo.

No caso de um arquivo de cabeçalho, se a usingdeclaração estiver no escopo do arquivo, ela se estenderá ao escopo de qualquer arquivo no qual o cabeçalho esteja incluído.

YoungJohn
fonte
2
você parece ser o único como entendeu a questão real ... no entanto, minha compilação não está muito feliz sobre eu usar dentro da desaceleração de classe.
enferrujado de
Essa resposta poderia ser ainda melhor explicando o problema com a ideia do OP de como o escopo deve funcionar (como o namespacematerial de declaração) versus como ele realmente funciona (como uma variável). {}envolvê-lo limita seu escopo, {}depois de não fazer nada relacionado a ele. Essa é uma forma acidental de using namespaceser aplicada globalmente.
TafT de
2

Eu acredito que você pode usar 'using' em cabeçalhos C ++ com segurança se você escrever suas declarações em um namespace aninhado como este:

namespace DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED
{
    /*using statements*/

    namespace DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED
    {
        /*declarations*/
    }
}

using namespace DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED::DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED;

Isso deve incluir apenas as coisas declaradas em 'DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED' sem os namespaces usados. Eu testei no compilador mingw64.

AnArrayOfFunctions
fonte
Esta é uma técnica útil que eu não tinha visto antes; obrigado. Normalmente, estou bem em usar a qualificação de escopo completo e colocar usingdeclarações dentro das definições de função onde posso para que não poluam os namespaces fora da função. Mas agora quero usar literais definidos pelo usuário C ++ 11 em um arquivo de cabeçalho e, pela convenção usual, os operadores literais são protegidos por um namespace; mas eu não quero usá-los em listas de inicializadores de construtor que não estão em um escopo que eu possa usar uma usingdeclaração não poluente . Portanto, isso é ótimo para resolver esse problema.
Anthony Hall
Embora um efeito colateral desse padrão é que todas as classes declaradas dentro do namespace mais interno vai aparecer em mensagens de erro do compilador com o nome totalmente qualificado: error: ... DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED:: DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED::ClassName .... Pelo menos, é isso que está acontecendo comigo no g ++.
Anthony Hall