As classes de utilitário com nada além de membros estáticos são um anti-padrão em C ++?

39

A pergunta Onde devo colocar funções que não estão relacionadas a uma classe provocou algum debate sobre se faz sentido em C ++ combinar funções utilitárias em uma classe ou apenas tê-las como funções livres em um espaço para nome.

Eu venho de um background em C # onde a última opção não existe e, portanto, naturalmente tendemos a usar classes estáticas no pequeno código C ++ que escrevo. A resposta mais votada para essa pergunta, bem como vários comentários, dizem que as funções livres devem ser preferidas, mesmo sugerindo que as classes estáticas eram um antipadrão. Por que isso acontece em C ++? Pelo menos na superfície, métodos estáticos em uma classe parecem indistinguíveis de funções livres em um espaço para nome. Por que, portanto, a preferência pelo último?

As coisas seriam diferentes se a coleção de funções utilitárias precisasse de alguns dados compartilhados, por exemplo, um cache que pudesse ser armazenado em um campo estático privado?

PersonalNexus
fonte
Parece um pouco com o antipadrão de "decomposição funcional".
User281377
7
Resposta curta: você não precisa de uma classe para agrupar essas funções. As funções livres são muito mais adequadas para sua tarefa do que agrupá-las em alguma construção de pseudo-OO, que é uma solução alternativa que você só precisa em linguagens "puramente-OO".
Chris diz Reinstate Monica

Respostas:

39

Eu acho que responder que devemos comparar as intenções de ambas as classes e namespaces. De acordo com a Wikipedia:

Classe

Na programação orientada a objetos, uma classe é uma construção usada como um blueprint para criar instâncias próprias - denominadas instâncias de classe, objetos de classe, objetos de instância ou simplesmente objetos. Uma classe define membros constituintes que permitem que essas instâncias de classe tenham estado e comportamento. Os membros do campo de dados (variáveis ​​de membro ou variáveis ​​de instância) permitem que um objeto de classe mantenha o estado. Outros tipos de membros, especialmente métodos, permitem o comportamento de um objeto de classe. As instâncias de classe são do tipo da classe associada.

Namespace

Em geral, um espaço para nome é um contêiner que fornece contexto para os identificadores (nomes ou termos técnicos ou palavras) que possui e permite a desambiguação de identificadores de homônimos que residem em diferentes espaços para nome.

Agora, o que você está tentando alcançar colocando as funções em uma classe (estaticamente) ou em um espaço para nome? Aposto que a definição de um espaço para nome descreve melhor sua intenção - tudo o que você deseja é um contêiner para suas funções. Você não precisa de nenhum dos recursos descritos na definição de classe. Observe que as primeiras palavras da definição de classe são " Na programação orientada a objetos ", mas não há nada orientado a objetos em uma coleção de funções.

Provavelmente, também existem razões técnicas, mas como alguém vindo de Java e tentando entender a linguagem de múltiplos paradigmas que é C ++, a resposta mais óbvia para mim é: porque não precisamos de OO para conseguir isso.

Gyan tcp Gary Buyn
fonte
1
+1 para "não precisamos de OO para conseguir isso"
Ixrec 5/01/15
Eu concordo com isso como não fã do OO, mas acho que existem algumas situações em que o uso de uma classe All-static é a única solução, por exemplo, substituindo um espaço para nome, pois você não pode declarar um espaço para nome aninhado dentro uma aula.
Wael Boutglay
26

Eu seria muito cauteloso em chamar isso de antipadrão. Os espaços para nome são geralmente preferidos, mas como não há modelos de espaço para nome e os espaços para nome não podem ser passados ​​como parâmetros do modelo, o uso de classes com nada além de membros estáticos é bastante comum.

AProgrammer
fonte
3
As outras respostas encobriram a última linha do OP. Os espaços para nome são escopos bastante nomeados; portanto, se você precisar de controle de acesso, as classes são a única ferramenta disponível.
Cmannett85
2
@ cbamber85 para ser justo, eu tinha colocado essa linha somente depois de ler as duas primeiras respostas.
PersonalNexus
@PersonalNexus Ah, é justo, eu não percebi!
Cmannett85
10
@ cbamber85: Isso não é totalmente verdade. Você obteria um encapsulamento ainda melhor colocando funções "privadas" no arquivo de implementação, para que os usuários não vejam nem mesmo a declaração privada.
UncleBens
2
Os modelos +1 são de fato um ponto em que os namespaces ainda carecem de recursos.
Chris diz Reinstate Monica
13

No dia em que eu precisava fazer uma aula de FORTRAN. Eu conhecia outras línguas imperativas até então, então achei que poderia fazer o FORTRAN sem muito estudo. Quando entreguei minha primeira lição de casa, o professor me devolveu e pediu para refazê-la: ele disse que os programas Pascal escritos na sintaxe FORTRAN não contam como envios válidos.

Problema semelhante está em jogo aqui: o uso de classes estáticas para hospedar funções utilitárias em C ++ é um idioma estrangeiro para C ++.

Como você mencionou na sua pergunta, o uso de classes estáticas para funções utilitárias em C # é uma necessidade: funções independentes simplesmente não são uma opção em C #. A linguagem necessária para desenvolver um padrão para permitir que os programadores definam funções independentes de alguma outra maneira - a saber, dentro das classes de utilidade estática. Este foi um truque de Java tomado palavra por palavra: por exemplo, java.lang.Math e System.Math do .NET são quase isomórficos.

O C ++, no entanto, oferece namespaces, um recurso diferente para atingir o mesmo objetivo e o usa ativamente na implementação de sua biblioteca padrão. Adicionar uma camada extra de classes estáticas não é apenas desnecessário, mas também um pouco contra-intuitivo para leitores sem C # ou Java background. Em certo sentido, você está introduzindo uma "tradução de empréstimo" no idioma para algo que possa ser expresso de forma nativa.

Quando suas funções precisam compartilhar dados, a situação é diferente. Como suas funções não são mais independentes , o padrão Singleton se torna a maneira preferida de atender a esse requisito.

dasblinkenlight
fonte
6
+1, exceto para recomendar singletons. Eles não são preferidos em C ++! Funções podem estar em uma classe se eles precisam estado share, mas as classes não deve ser singletons menos que eles realmente, realmente precisa ser. E eles geralmente não.
Maxpm
@ Maxpm Não me interpretem mal, o singleton é preferido apenas para variáveis ​​estáticas globais. Fora isso, não há nada "preferido" nisso.
dasblinkenlight
2
Existe este bom artigo, Singletons: Solucionando problemas que você não sabia que nunca teve desde 1995 . Resumido, ele diz que o acoplamento da instância conhecida à própria classe limita desnecessariamente sua flexibilidade e não oferece nada. Na maioria das vezes, apenas tem uma classe e uma instância compartilhada em algum lugar, mas não a torne única.
Jan Hudec
Não sei se "idioma estrangeiro" é o termo certo. É um idioma muito comum. Eu acho que algumas equipes de programação estiver usando e2 de Stroustrup ...
Nick Keighley
10

Talvez você precise perguntar por que deseja uma classe totalmente estática?

A única razão pela qual consigo pensar é que outras linguagens (Java e C #) que são muito 'tudo é uma classe' as exigem. Como essas linguagens não podem criar funções de nível superior, inventaram um truque para mantê-las, e essa era a função membro estática. Eles são um pouco de solução alternativa, mas o C ++ não precisa disso, você pode criar novas funções independentes de nível superior diretamente.

Se você precisar de funções que operam em um item de dados específico, faz sentido agrupá-las em uma classe que contém os dados, mas essas funções deixam de ser funções e passam a ser membros dessa classe que operam nos dados da classe.

Se você tem uma função que não opera em um tipo de dados específico (eu uso a palavra aqui, pois as classes são formas de definir novos tipos de dados), então é realmente um anti-padrão colocá-los em uma classe, por nada além de fins semânticos.

gbjbaanb
fonte
10
Na verdade - eu diria que a "classe com todos os membros estáticos" em Java é realmente um truque para contornar uma limitação do Java.
Charles Salvia
@CharlesSalvia: Eu estava sendo educado :) Tenho o mesmo mau pressentimento sobre main () fazer parte de uma classe java também, apesar de entender por que eles fizeram isso.
Gbjbaanb
Na verdade, isso parece fornecer a resposta para o motivo de você querer uma classe totalmente estática. Ou eu te entendo mal? Por exemplo, preciso manter uma variável cujo valor possa mudar, que é lido por uma classe (que pretendo armazenar na ROM) e gravado por outra classe (que estará na RAM). Simplesmente colocar o local em uma ram conhecida não é suficiente - agrupá-lo (e seus acessadores) em uma classe permite que eu ajuste o controle de acesso. Praticamente o que você descreve em seu segundo parágrafo.
iheanyi
5

Pelo menos na superfície, métodos estáticos em uma classe parecem indistinguíveis de funções livres em um espaço para nome.

Em outras palavras, ter uma classe em vez de um espaço para nome não tem vantagens.

Por que, portanto, a preferência pelo último?

Por um lado, você economiza digitando statico tempo todo, embora esse seja um benefício menor.

O principal benefício é que é a ferramenta menos poderosa para o trabalho. As classes podem ser usadas para criar objetos, elas podem ser usadas como o tipo de variáveis ​​ou como argumentos de modelo. Nenhum desses são os recursos que você deseja para sua coleção de funções. Portanto, é preferível usar uma ferramenta que não tenha esses recursos, para que a classe não possa ser acidentalmente usada indevidamente.

Depois disso, o uso de um espaço para nome também deixa imediatamente claro para qualquer usuário do seu código que essa é uma coleção de funções e não um modelo para a criação de objetos.

sepp2k
fonte
5

... vários comentários, no entanto, dizem que as funções livres devem ser preferidas, mesmo sugerindo que as classes estáticas sejam um anti-padrão. Por que isso acontece em C ++? Pelo menos na superfície, métodos estáticos em uma classe parecem indistinguíveis de funções livres em um espaço para nome. Por que, portanto, a preferência pelo último?

Uma classe totalmente estática fará o trabalho, mas é como dirigir um caminhão de 53 pés até a mercearia para comprar salgadinhos e salsa quando um sedan de quatro portas fará (ou seja, é um exagero). As aulas vêm com uma pequena quantidade de sobrecarga adicional e sua existência pode dar a alguém a impressão de que instanciar uma pode ser uma boa idéia. As funções gratuitas oferecidas pelo C ++ (e C, onde todas as funções são gratuitas) não fazem isso; são apenas funções e nada mais.

As coisas seriam diferentes se a coleção de funções utilitárias precisasse de alguns dados compartilhados, por exemplo, um cache que pudesse ser armazenado em um campo estático privado?

Na verdade não. O objetivo original do staticC (e posterior do C ++) era oferecer armazenamento persistente que pode ser usado em qualquer nível de escopo:

int counter() {
    static int value = 0;
    value++;
}

Você pode levar o escopo para o nível de um arquivo, o que o torna visível para todos os escopos mais estreitos, mas não fora do arquivo:

static int value = 0;

int up() { value++; }
int down() { value--; }

Um membro da classe estática privada em C ++ serve ao mesmo propósito, mas está limitado ao escopo de uma classe. A técnica usada no counter()exemplo acima também funciona dentro dos métodos C ++ e é algo que eu realmente recomendo fazer se a variável não precisar ser visível para toda a classe.

Blrfl
fonte
Suponho que um grupo de funções utilitárias não relacionadas que não compartilham dados deva ser melhor agrupado em um espaço para nome. Mas se você tiver um grupo de funções utilitárias fortemente acopladas que precisam acessar dados compartilhados e talvez algum controle de acesso, uma classe estática é a melhor opção. Uma estática dentro de uma função não é reentrada. Usando um módulo global ... um pouco melhor nesse contexto, mas ainda acho que uma classe estática é a melhor solução se você tiver os requisitos que descrevi.
iheanyi
Se eles compartilharem dados, poderiam ser melhores como uma classe sem métodos estáticos?
Nick Keighley
@NickKeighley Isso depende de quem vence o debate sobre se é melhor criar uma instância e passá-la para tudo o que precisa ou apenas torná-la estática na classe.
Blrfl
1

Se uma função não mantém um estado e é reentrada, parece que não faz muito sentido empurrá-la para dentro de uma classe (a menos que seja forçada pelo idioma). Se a função mantiver algum estado (por exemplo, ela pode ser tornada segura para threads por meio de um mutex estático), os métodos estáticos em uma classe parecem adequados.

Martin James
fonte
1

Grande parte do discurso sobre o assunto aqui faz sentido, embora exista algo muito fundamental sobre C ++ que faça namespacese classes / structs muito diferentes.

Classes estáticas (classes em que todos os membros são estáticos e a classe nunca será instanciada) são objetos em si. Eles não são simplesmente um namespacepara conter funções.

A meta-programação de modelos nos permite usar uma classe estática como um objeto em tempo de compilação.

Considere isto:

template<typename allocator_type> class allocator
{
public:
    inline static void* allocate(size_t size)
    {
        return allocator_type::template allocate(size);
    }
    inline static void release(void* p)
    {
        allocator_type::template release(p);
    }
};

Para usá-lo, precisamos de funções contidas em uma classe. Um espaço para nome não funcionará aqui. Considerar:

class mallocator
{
    inline static void* allocate(size_t size)
    {
        return std::malloc(size);
    }
    inline static void release(void* p)
    {
        return std::free(p);
    }
};

Agora, para usá-lo:

using my_allocator = allocator<mallocator>;

void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);

Desde que um novo alocador exponha uma função allocatee releaseque seja compatível, é fácil alternar para um novo alocador.

Isso não pode ser alcançado com espaços para nome.

Você sempre precisa de funções para fazer parte de uma classe? Não.

O uso de uma classe estática é um anti-padrão? Depende do contexto.

As coisas seriam diferentes se a coleção de funções utilitárias precisasse de alguns dados compartilhados, por exemplo, um cache que pudesse ser armazenado em um campo estático privado?

Nesse caso, o que você está tentando alcançar provavelmente será melhor atendido por meio de programação orientada a objetos.

Michael Gazonda
fonte
O uso da palavra-chave inline é redundante aqui, pois os métodos definidos em uma definição de classe são implicitamente inline. Consulte en.cppreference.com/w/cpp/language/inline .
Trevor
1

Como John Carmack disse:

"Às vezes, a implementação elegante é apenas uma função. Não é um método. Não é uma classe. Não é uma estrutura. Apenas uma função."

Eu acho que isso resume tudo. Por que você faria dessa classe uma força, se claramente não é uma classe? C ++ tem o luxo de que você pode realmente usar funções; em Java, tudo é um método. Então você precisa de classes de utilidade, e muitas delas.

juhist
fonte