Maneira correta de definir métodos de namespace C ++ no arquivo .cpp

108

Provavelmente uma duplicata, mas não é fácil de pesquisar ...

Dado um cabeçalho como:

namespace ns1
{
 class MyClass
 {
  void method();
 };
}

Eu vi method()definido de várias maneiras no arquivo .cpp:

Versão 1:

namespace ns1
{
 void MyClass::method()
 {
  ...
 }
}

Versão 2:

using namespace ns1;

void MyClass::method()
{
 ...
}

Versão 3:

void ns1::MyClass::method()
{
 ...
}

Existe uma maneira 'certa' de fazer isso? Alguma dessas coisas está "errada" no sentido de que não significam a mesma coisa?

Senhor menino
fonte
Na maioria dos códigos, geralmente vejo a terceira versão (mas não vejo o motivo: D), a segunda versão é simplesmente o oposto do motivo pelo qual os namespaces foram introduzidos, eu acho.
Sr.Anubis
1
Como um fato interessante, as ferramentas de refatoração do Visual Assist ficam felizes em gerar código usando o nº 1 ou o nº 3, dependendo de qual estilo já está em uso no arquivo de destino.
Sr. Boy

Respostas:

51

A versão 2 não é clara e fácil de entender porque você não sabe a qual namespace MyClasspertence e é simplesmente ilógica (a função de classe não está no mesmo namespace?)

A versão 1 está certa porque mostra que, no namespace, você está definindo a função.

A versão 3 também está certa porque você usou o ::operador de resolução de escopo para se referir ao MyClass::method ()no namespace ns1. Eu prefiro a versão 3.

Consulte Namespaces (C ++) . Esta é a melhor maneira de fazer isso.

GILGAMESH
fonte
22
Chamar # 2 de "errado" é um grande exagero. Por essa lógica, todos os nomes de símbolo estão "errados" porque podem potencialmente ocultar outros nomes de símbolo em outros escopos.
dez
É ilógico. Ele irá compilar bem (desculpe, não entendi bem na resposta), mas por que você definiria uma função fora do seu namespace? Isso confunde o leitor. Além disso, quando muitos namespaces são usados, ele não mostra a qual namespace MyClass pertence. As versões 1 e 3 corrigem esse problema. Em conclusão, não é errado, mas apenas confuso e confuso.
GILGAMESH
3
Concordo com @PhoenicaMacia, o truque de uso é horrível e pode causar confusão. Considere uma classe que implementa um operador como uma função livre, no cabeçalho que você teria namespace N {struct X { void f(); }; X operator==( X const &, X const & ); }, agora no arquivo cpp com a instrução using você pode definir a função de membro como void X::f() {}, mas se você definir X operator==(X const&, X const&)você estará definindo um operador diferente daquele definido no cabeçalho (você terá que usar 1 ou 3 para a função gratuita lá).
David Rodríguez - dribeas
1
Em particular, eu prefiro 1, e o exemplo no artigo vinculado realmente não resolve nada que o primeiro exemplo usa 1, o segundo usa uma combinação de 1 e 3 (as funções são definidas com qualificação, mas são definidas dentro do namespace externo)
David Rodríguez - dribeas
1
Dos 3, eu diria que 1) é o melhor, no entanto, a maioria dos IDEs tem o hábito um tanto chato de recuar tudo dentro dessa declaração de namespace e isso adiciona alguma confusão.
locka de
28

5 anos depois e pensei em mencionar isso, que parece bom e não é mau

using ns1::MyClass;

void MyClass::method()
{
  // ...
}
Puzomor Croácia
fonte
3
Esta é a melhor resposta. Parece o mais limpo e evita os problemas com as Versões 1 do OP, que podem trazer coisas para o espaço de nomes involuntariamente, e 2, que podem trazer coisas para o espaço global sem querer.
ayane_m
Sim, essa é uma ótima combinação de menos digitação do que 3, embora ainda declare explicitamente a intenção.
jb
14

Estou usando a versão 4 (abaixo) porque combina a maioria das vantagens da versão 1 (concisão da definição resoetiva) e da versão 3 (ser explícito ao máximo). A principal desvantagem é que as pessoas não estão acostumadas, mas como eu o considero tecnicamente superior às alternativas, não me importo.

Versão 4: use qualificação completa usando aliases de namespace:

#include "my-header.hpp"
namespace OI = outer::inner;
void OI::Obj::method() {
    ...
}

No meu mundo, estou freqüentemente usando aliases de namespace, pois tudo é explicitamente qualificado - a menos que não possa (por exemplo, nomes de variáveis) ou seja um ponto de personalização conhecido (por exemplo, swap () em um modelo de função).

Dietmar Kühl
fonte
1
Embora eu ache que a lógica "é melhor, então não me importo se isso confunde as pessoas" é falha, devo concordar que essa é uma boa abordagem para namespaces aninhados.
Mr. Boy
1
1 para a excelente ideia "por que não pensei nisso"! (Quanto a "as pessoas não estão acostumadas a [coisas novas tecnicamente superiores]", elas se acostumarão se mais pessoas fizerem isso.)
wjl
Apenas para ter certeza de que entendi, ambos outere innerjá estão definidos como namespaces em outros arquivos de cabeçalho?
dekuShrub
4

A versão 3 torna a associação entre a classe e o namespace muito explícita à custa de mais digitação. A versão 1 evita isso, mas captura a associação com um bloco. A versão 2 tende a esconder isso, então eu evito aquela.

Paul Joireman
fonte
3

Eu escolho Num.3 (também conhecido como a versão detalhada). É mais digitação, mas a intenção é exata para você e para o compilador. O problema que você postou no estado em que se encontra é, na verdade, mais simples do que o mundo real. No mundo real, existem outros escopos de definições, não apenas os membros da classe. Suas definições não são muito complicadas apenas com classes - porque seu escopo nunca é reaberto (ao contrário de namespaces, escopo global, etc.).

Num.1 isso pode falhar com escopos diferentes de classes - qualquer coisa que possa ser reaberta. Portanto, você pode declarar uma nova função em um namespace usando essa abordagem, ou seus inlines podem acabar sendo substituídos via ODR. Você precisará disso para algumas definições (notadamente, especializações de modelo).

Num.2 Isso é muito frágil, particularmente em grandes bases de código - conforme os cabeçalhos e dependências mudam, seu programa irá falhar ao compilar.

Num.3 Isso é ideal, mas muito para digitar - qual sua intenção é definir algo . Isso faz exatamente isso, e o compilador entra em ação para garantir que você não cometeu um erro, uma definição não está fora de sincronia com sua declaração, etc.

justin
fonte
2

Todos os caminhos são corretos e cada um tem suas vantagens e desvantagens.

Na versão 1, você tem a vantagem de não precisar escrever o namespace na frente de cada função. A desvantagem é que você obterá uma identificação enfadonha, especialmente se tiver mais de um nível de namespaces.

Na versão 2, você deixa seu código mais limpo, mas se tiver mais de um namespace sendo implementado no CPP, um pode acessar as funções e variáveis ​​do outro diretamente, tornando seu namespace inútil (para aquele arquivo cpp).

Na versão 3, você terá que digitar mais e suas linhas de função podem ser maiores do que a tela, o que é ruim para efeitos de design.

Há também outra maneira como algumas pessoas o usam. É semelhante à primeira versão, mas sem os problemas de identificação.

É tipo isso:

#define OPEN_NS1 namespace ns1 { 
#define CLOSE_NS1 }

OPEN_NS1

void MyClass::method()
{
...
}

CLOSE_NS1

Cabe a você escolher qual é o melhor para cada situação =]

Renan Greinert
fonte
14
Não vejo razão para usar uma macro aqui. Se você não quiser recuar, apenas não recue. Usar uma macro apenas torna o código menos óbvio.
dez
1
Acho que a última versão que você mencionou é útil sempre que você deseja compilar seu código com compiladores antigos que não suportam namespaces (sim, alguns dinossauros ainda estão por aí). Nesse caso, você pode colocar a macro dentro de uma #ifdefcláusula.
Luca Martini
Você não precisa se identificar se não quiser, mas se não usar macros, alguns IDEs tentarão fazer isso por você. Por exemplo, no Visual Studio, você pode selecionar todo o código e pressionar ALT + F8 para identificar automaticamente. Se você não usar as definições, perderá essa funcionalidade. Além disso, não acho que OPEN_ (namespace) e CLOSE_ (namespace) sejam menos óbvios, se você os tiver em seu padrão de codificação. O motivo dado por @LucaMartini também é interessante.
Renan Greinert
Se isso fosse genérico, ou seja #define OPEN_NS(X), acho que é um pouco útil, mas não realmente ... Eu não me oponho a macros, mas isso parece um pouco OTT. Acho que a abordagem de Dietmar Kühl é melhor para namespaces aninhados.
Mr. Boy