Namespace + funções versus métodos estáticos em uma classe

290

Digamos que eu tenho, ou vou escrever, um conjunto de funções relacionadas. Digamos que eles estejam relacionados à matemática. Organizacionalmente, devo:

  1. Escreva essas funções e coloque-as no meu MyMathnamespace e consulte-as viaMyMath::XYZ()
  2. Crie uma classe chamada MyMathe torne esses métodos estáticos e consulte a mesmaMyMath::XYZ()

Por que eu escolheria um sobre o outro como forma de organizar meu software?

RobertL
fonte
4
por um lado, os namespaces são uma adição mais recente à linguagem, em comparação com classes e métodos estáticos, que estavam na linguagem desde o momento em que foi chamado "C com classes". Alguns programadores podem se sentir mais confortáveis ​​com os recursos mais antigos. Alguns outros programadores podem estar usando compiladores antigos. Apenas meu $ .02 #
094
21
@ Rom: Você está certo sobre "programadores antigos", mas errado sobre "compiladores antigos". Os namespaces são compilados corretamente desde éons (trabalhei com eles no Visual C ++ 6, datado de 1998!). Quanto ao "C com classes", algumas pessoas deste fórum nem nasceram quando isso aconteceu: Usar isso como argumento para evitar um recurso C ++ padrão e generalizado é uma falácia. Em conclusão, apenas compiladores C ++ obsoletos não suportam espaços de nome. Não use esse argumento como desculpa para não usá-los.
paercebal
@ paercebal: alguns compiladores antigos ainda estão em uso no mundo incorporado. O fato de não oferecer suporte a namespaces é provavelmente um dos menores inconvenientes que você precisa suportar ao escrever um código para várias pequenas CPUs com as quais todos interagem todos os dias: seu aparelho de som, seu micro-ondas, a unidade de controle do motor em seu carro, o semáforo etc. seja claro: não estou defendendo o uso de compiladores melhores e mais novos em todos os lugares. Au contrrare: Sou a favor dos recursos de idiomas mais recentes (exceto RTTI;)). Estou apenas salientando que existe uma tendência desse tipo #
Rom Rom
13
@ Rom: No caso atual, o autor da pergunta tem a opção, aparentemente, nenhum de seus compiladores falha ao compilar um código no namespace. E como essa é uma pergunta sobre C ++, uma resposta em C ++ deve ser fornecida, incluindo a referência a namespaces e soluções RTTI para o problema, se necessário. Dar uma resposta C ou C-com-classes-para-compiladores obsoletos está fora de tópico.
paercebal
2
"Just my $ .02" - valeria mais se você fornecesse alguma evidência de compiladores C ++ existentes que não suportam namespaces. "alguns compiladores antigos ainda estão em uso no mundo incorporado" - isso é bobagem; o "mundo incorporado" é um desenvolvimento mais recente que os namespaces C ++. O "C com classes" foi introduzido em 1979, muito antes de alguém incorporar o código C em qualquer coisa. "Estou apenas apontando que essa tendência existe" - mesmo que sim, isso não tem nada a ver com essa questão.
Jim Balter

Respostas:

243

Por padrão, use funções com espaço para nome.

As classes são para criar objetos, não para substituir espaços para nome.

No código Orientado a Objetos

Scott Meyers escreveu um item inteiro para seu livro Effective C ++ sobre este tópico, "Prefira funções de não-membro não-amigo a funções de membro". Encontrei uma referência on-line a esse princípio em um artigo da Herb Sutter:http://www.gotw.ca/gotw/084.htm

O importante é saber: No C ++, as funções no mesmo espaço de nomes que uma classe pertencem à interface dessa classe (porque o ADL pesquisará essas funções ao resolver chamadas de função).

Funções com espaço para nome, a menos que declarado "amigo", não têm acesso aos internos da classe, enquanto métodos estáticos.

Isso significa, por exemplo, que, ao manter sua classe, se você precisar alterar os componentes internos da sua classe, precisará procurar efeitos colaterais em todos os seus métodos, incluindo os estáticos.

Extensão I

Adicionando código à interface de uma classe.

Em C #, você pode adicionar métodos a uma classe, mesmo se você não tiver acesso a ela. Mas em C ++, isso é impossível.

Mas, ainda em C ++, você ainda pode adicionar uma função com espaço para nome, mesmo para uma classe que alguém escreveu para você.

Veja do outro lado, isso é importante ao projetar seu código, porque, ao colocar suas funções em um espaço para nome, você autorizará seus usuários a aumentar / concluir a interface da classe.

Extensão II

Um efeito colateral do ponto anterior, é impossível declarar métodos estáticos em vários cabeçalhos. Todo método deve ser declarado na mesma classe.

Para espaços para nome, funções do mesmo espaço para nome podem ser declaradas em vários cabeçalhos (a função de troca quase padrão é o melhor exemplo disso).

Extensão III

O frescor básico de um espaço para nome é que, em algum código, você pode evitar mencioná-lo, se você usar a palavra-chave "using":

#include <string>
#include <vector>

// Etc.
{
   using namespace std ;
   // Now, everything from std is accessible without qualification
   string s ; // Ok
   vector v ; // Ok
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

E você pode até limitar a "poluição" a uma classe:

#include <string>
#include <vector>

{
   using std::string ;
   string s ; // Ok
   vector v ; // COMPILATION ERROR
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

Esse "padrão" é obrigatório para o uso adequado do idioma de troca quase padrão.

E isso é impossível de se fazer com métodos estáticos nas classes.

Portanto, os namespaces do C ++ têm sua própria semântica.

Mas vai além, pois você pode combinar espaços para nome de maneira semelhante à herança.

Por exemplo, se você tiver um espaço para nome A com uma função AAA, um espaço para nome B com uma função BBB, poderá declarar um espaço para nome C e trazer AAA e BBB nesse espaço para nome com a palavra-chave using.

Conclusão

Os espaços para nome são para espaços para nome. As aulas são para aulas.

O C ++ foi projetado para que cada conceito seja diferente e seja usado de maneira diferente, em casos diferentes, como uma solução para diferentes problemas.

Não use classes quando precisar de espaços para nome.

E no seu caso, você precisa de espaços para nome.

paercebal
fonte
Essa resposta também pode ser aplicada aos threads, ou seja, é melhor usar espaços para nome em vez de métodos estáticos para threads?
dashesy
3
@ dashesy: namespaces vs. métodos estáticos não têm nada a ver com threads, então sim, namespaces são melhores porque namespaces são quase sempre melhores que métodos estáticos. Os métodos estáticos têm acesso a variáveis ​​de membros da classe e, de alguma forma, têm um valor de encapsulamento menor do que os espaços para nome. E isolar dados é ainda mais importante na execução encadeada.
21412 paercebal
@ paercebal- obrigado, eu estava usando os métodos de classe estática para funções de thread. Agora eu entendo que estava usando mal a classe como espaço para nome, então qual você acha que é a melhor abordagem para ter vários threads em um objeto? Eu fiz esta pergunta no SO também, eu apreciaria se você pode lançar alguma luz (aqui ou na própria questão)
dashesy
1
@ dashesy: ​​Você está pedindo problemas. O que você deseja com diferentes threads é isolar os dados que não devem ser compartilhados, portanto, ter vários threads com acesso privilegiado aos dados privados de uma classe é uma má idéia. Eu ocultaria um encadeamento dentro de uma classe e isolaria os dados desse encadeamento dos dados do encadeamento principal. Obviamente, os dados que devem ser compartilhados podem ser membros dessa classe, mas ainda devem ser sincronizados (bloqueios, atômicos etc.). Não tenho certeza de quantas libs você tem acesso, mas o uso de tasks / async é ainda melhor.
21412 paercebal
a resposta de paercebal deve ser a aceita! Apenas mais um link para o swap quase padrão () por meio de namespace + ADL -> stackoverflow.com/questions/6380862/… #
Gob00st
54

Muitas pessoas discordam de mim, mas é assim que eu vejo:

Uma classe é essencialmente uma definição de um certo tipo de objeto. Os métodos estáticos devem definir operações intimamente ligadas a essa definição de objeto.

Se você deseja apenas ter um grupo de funções relacionadas não associadas a um objeto subjacente ou a definição de um tipo de objeto , então eu diria que vá apenas com um espaço para nome. Só para mim, conceitualmente, isso é muito mais sensato.

Por exemplo, no seu caso, pergunte a si mesmo: "O que é um MyMath?" Se MyMathnão define um tipo de objeto, então eu diria: não faça disso uma classe.

Mas, como eu disse, sei que há muitas pessoas que (até veementemente) discordam de mim sobre isso (em particular, desenvolvedores de Java e C #).

Dan Tao
fonte
3
Você tem uma perspectiva muito pura sobre isso. Mas praticamente falando, uma classe com todos os-estático métodos pode vir a calhar: pode typedef-los, usá-los como parâmetros do modelo, etc.
Shog9
56
Isso porque as pessoas Jave e C # não têm escolha.
Martin York
7
@ shog9. Você também pode modelar as funções!
Martin York
6
@ Dan: presumivelmente, um que precisasse de rotinas matemáticas e quisesse "implantar" diferentes implementações.
Shog9
1
@ Dan: "Eu acho que se alguém estiver interessado em usar uma classe como parâmetro de modelo, essa classe está quase certamente definindo algum objeto subjacente." Não, não mesmo. Pense em traços. (No entanto, concordo plenamente com a sua resposta.)
SBI
18
  • Se você precisar de dados estáticos, use métodos estáticos.
  • Se forem funções de modelo e você desejar especificar um conjunto de parâmetros de modelo para todas as funções, use métodos estáticos em uma classe de modelo.

Caso contrário, use funções com espaço para nome.


Em resposta aos comentários: sim, métodos estáticos e dados estáticos tendem a ser superutilizados. Por isso, ofereci apenas dois cenários relacionados , onde acho que podem ser úteis. No exemplo específico do OP (um conjunto de rotinas matemáticas), se ele desejasse a capacidade de especificar parâmetros - digamos, um tipo de dados principal e precisão de saída - que seriam aplicados a todas as rotinas, ele poderia fazer algo como:

template<typename T, int decimalPlaces>
class MyMath
{
   // routines operate on datatype T, preserving at least decimalPlaces precision
};

// math routines for manufacturing calculations
typedef MyMath<double, 4> CAMMath;
// math routines for on-screen displays
typedef MyMath<float, 2> PreviewMath;

Se você não precisa que, em seguida, por todos os meios utilizar um espaço de nomes.

Shog9
fonte
2
os chamados dados estáticos podem ser dados no nível do espaço para nome no arquivo de implementação do espaço para nome, o que reduz ainda mais o acoplamento, pois não precisa aparecer no cabeçalho.
Motti
Os dados estáticos não são melhores que os globais do espaço para nome.
Coppro
@coppro. Eles são pelo menos um passo na cadeia evolutiva a partir de globais aleatórios, pois podem ser tornados privados (mas de outra forma concordam).
Martin York
@Motti: OTOH, se você quiser no cabeçalho (funções embutidas / modelo), você voltará a ser feio.
Shog9
1
Exemplo interessante: usar uma classe como abreviação para evitar repetir templateargumentos!
underscore_d
13

Você deve usar um espaço para nome, porque um espaço para nome tem muitas vantagens sobre uma classe:

  • Você não precisa definir tudo no mesmo cabeçalho
  • Você não precisa expor toda a sua implementação no cabeçalho
  • Você não pode ser usingum membro da classe; você pode usingum membro do espaço para nome
  • Você não pode using class, embora using namespacenem sempre seja uma boa ideia
  • O uso de uma classe implica que existe algum objeto a ser criado quando realmente não existe

Membros estáticos são, na minha opinião, muito, muito usados. Eles não são uma necessidade real na maioria dos casos. As funções de membros estáticos provavelmente são melhores como funções de escopo de arquivo, e os membros de dados estáticos são apenas objetos globais com uma reputação melhor e não merecida.

coppro
fonte
3
"Você não precisa expor toda a sua implementação no cabeçalho" nem o faz quando usa uma classe.
Vanuan
Ainda mais: se você estiver usando espaços para nome, não poderá expor toda a sua implementação no cabeçalho (você terá várias definições de símbolos). As funções de membro da classe em linha permitem que você faça isso.
Vanuan
1
@Vanuan: Você pode expor implementações de namespace no cabeçalho. Basta usar a inlinepalavra-chave para satisfazer o ODR.
Thomas Eding
@ThomasEding não precisa = pode!
Vanuan
3
@Vanuan: Apenas uma coisa é garantida pelo compilador ao usar inline, e NÃO é "embutido" no corpo de uma função. O objetivo real (e garantido pelo padrão) do inlineé impedir várias definições. Leia sobre "Regra de uma definição" para C ++. Além disso, a questão do SO vinculado não estava sendo compilada devido a problemas de cabeçalho pré-compilado em vez de problemas de ODR.
Thomas Eding
3

Eu preferiria os espaços para nome, dessa forma, você pode ter dados privados em um espaço para nome anônimo no arquivo de implementação (para que ele não precise aparecer no cabeçalho, em oposição aos privatemembros). Outro benefício é que, pelo usingseu namespace, os clientes dos métodos podem optar por não especificarMyMath::

Motti
fonte
2
Você também pode ter dados privados em um espaço para nome anônimo no arquivo de implementação com classes. Não tenho certeza se eu sigo sua lógica.
Patrick Johnmeyer
0

Mais um motivo para usar a classe - Opção para fazer uso de especificadores de acesso. Em seguida, é possível quebrar seu método estático público em métodos particulares menores. O método público pode chamar vários métodos particulares.

Milind T
fonte
6
Os modificadores de acesso são legais, mas mesmo o privatemétodo mais é mais acessível que um método cujo protótipo não é publicado em nenhum cabeçalho (e, portanto, permanece invisível). Não estou nem mencionando o melhor encapsulamento oferecido por funções anônimas no espaço de nomes.
paercebal
2
Os métodos privados são inferiores à ocultação da função na implementação (arquivo cpp) e nunca a expõem no arquivo de cabeçalho. Por favor, elabore isso em sua resposta e por que você prefere usar membros privados . Até então -1.
Nonsensickle
@nonsensickle Talvez ele queira dizer que uma função gigantesca com muitas seções repetidas pode ser interrompida com segurança, enquanto oculta as subseções ofensivas por trás do privado, impedindo que outras pessoas os atinjam se forem perigosas / precisam de um uso muito cuidadoso.
Troyseph
1
@Troyseph, mesmo assim, você pode ocultar essas informações dentro de um espaço para nome sem nome no .cpparquivo, o que tornaria privado a unidade de tradução sem fornecer informações supérfluas a quem estiver lendo o arquivo de cabeçalho. Efetivamente, estou tentando defender o idioma do PIMPL.
Nonsensickle
Você não pode colocá-lo em um .cpparquivo se desejar usar modelos.
Yay295
0

O espaço para nome e o método de classe têm seus usos. O espaço para nome tem a capacidade de se espalhar pelos arquivos, no entanto, isso é um ponto fraco se você precisar aplicar todo o código relacionado para que ele vá em um arquivo. Como mencionado acima, a classe também permite criar membros estáticos privados na classe. Você pode tê-lo no espaço para nome anônimo do arquivo de implementação, no entanto, ainda é um escopo maior do que tê-lo dentro da classe.

rocd
fonte
"[armazenar coisas] no espaço de nomes anônimo do arquivo de implementação [é] um escopo maior do que tê-los dentro da classe" - não, não é. nos casos em que o acesso privilegiado aos membros não é necessário, as coisas no espaço de nomes anonimamente são mais particulares que as private:outras. e em muitos casos em que o acesso privilegiado parece ser necessário, isso pode ser fatorado. a função mais 'privada' é aquela que não aparece em um cabeçalho. private:Os métodos nunca podem usufruir desse benefício.
underscore_d