Onde devo colocar funções que não estão relacionadas a uma classe?


47

Estou trabalhando em um projeto C ++ em que tenho várias funções matemáticas que escrevi inicialmente para usar como parte de uma classe. Enquanto escrevia mais código, porém, percebi que precisava dessas funções matemáticas em todo lugar.

Onde é o melhor lugar para colocá-los? Digamos que eu tenho isso:

class A{
    public:
        int math_function1(int);
        ...
}

E quando escrevo outra aula, não posso (ou pelo menos não sei como) usá-lo math_function1nessa outra aula. Além disso, percebi que algumas dessas funções não estão realmente relacionadas à classe A. Elas pareciam estar no começo, mas agora posso ver como são apenas funções matemáticas.

Quais são as boas práticas nesta situação? No momento, eu os copio e colo nas novas classes, o que, com certeza, é a pior prática.


11
Você aprendeu sobre a staticpalavra - chave?
S.Lott

30
No C ++, as funções livres são quase sempre preferidas às funções-membro.
Pubby

4
Não existe uma regra que diga que tudo deve estar em uma classe. Pelo menos não em C ++.
tdammers

2
Eu prefiro um espaço de nomes para uma classe com um monte de métodos estáticos
Nick Keighley

Respostas:


70

O C ++ pode ter funções não-método muito bem, se elas não pertencerem a uma classe, não as coloque em uma classe, apenas as coloque no escopo de namespace global ou outro

namespace special_math_functions //optional
{
    int math_function1(int arg)
    {
         //definition 
    }
}

6
+1 Esta é a solução mais sensata, embora o espaço para nome extra não pareça necessário.
Pubby

1
não, não é necessário
jk.

27
Algum espaço para nome é útil para reduzir possíveis colisões de nomes com outras bibliotecas.
Bill Porta

11
Usar um espaço para nome também é bom porque desambigua se a chamada é um método ou uma função. ( math_function1(42)pode chamar um membro da classe atual; special_math_functions::math_function1(42)está claramente chamando uma função independente). Dito isto, ::math_function(42)fornece a mesma desambiguação.
ipeet

2
Os espaços para nome não são necessários, mas também não são proibidos. Daí porque esta resposta diz // optional. Tempere de acordo com o gosto.
User253751

6

Depende de como o projeto está organizado e que tipo de padrões de design você está usando, assumindo que este seja um código estritamente utilitário, você tem as seguintes opções:

  • Se você não precisar usar objetos para tudo, poderá fazer algo simples, como colocar todos eles em um arquivo sem um wrapper de classe ao seu redor. Pode ser com ou sem um espaço para nome, embora o espaço para nome seja recomendado para evitar problemas no futuro.
  • Para C ++ gerenciado, você pode criar uma classe estática para conter todos eles; no entanto, isso realmente não funciona da mesma forma que uma classe real e meu entendimento é que é um anti-padrão C ++.
  • Se você não estiver usando C ++ gerenciado, poderá usar funções estáticas para permitir que você as acesse e todas elas contidas em uma única classe. Isso pode ser útil se também houver outras funções para as quais você deseja um objeto instanciado adequado, também pode ser um anti-padrão.
  • Se você deseja garantir que apenas uma instância do objeto que contém as funções existirá, você pode usar o Padrão Singleton para uma classe de utilitário que também permite flexibilidade no futuro, pois agora você tem acesso a atributos não estáticos. Isso será de uso limitado e só se aplica se você precisar de um objeto por algum motivo. As probabilidades são de que, se você fizer isso, já saberá o porquê.

Observe que a primeira opção será a melhor aposta e as três seguintes são de utilidade limitada. Dito isso, você pode encontrar apenas devido a programadores de C # ou Java realizarem algum trabalho em C ++ ou se você trabalhar em código C # ou Java em que o uso de classes é obrigatório.


Por que o voto negativo?
rjzii

10
Eu não sou o menos favorável, mas provavelmente porque você está aconselhando uma classe com funções estáticas ou um singleton, enquanto funções livres provavelmente seriam boas nesse caso (e são aceitáveis ​​e úteis para muitas coisas em C ++).
Anton Golov

@AntonGolov - Funções gratuitas são a primeira coisa que eu mencionei na lista. :) O restante deles são as abordagens mais orientadas para OOP para situações em que você está lidando com "Tudo deve ser uma classe!" ambientes.
rjzii

9
@ Rob Z: No entanto, C ++ não é um daqueles "Tudo deve ser uma classe!" ambientes.
David Thornley

1
Desde quando é OPO forçar funções puras para uma classe? Parece mais com OOP-cargo-cult.
Deduplicator

1

Como você já disse, copiar e colar o código é a pior forma de reutilização de código. Se você possui funções que não pertencem a nenhuma de suas classes ou podem ser usadas em vários cenários, o melhor lugar para colocá-las seria uma classe auxiliar ou de utilidade. Se eles não usarem nenhum dado de instância, eles poderão se tornar estáticos, portanto, você não precisará criar uma instância da classe de utilitário para usá-lo.

Consulte aqui para obter uma discussão sobre funções-membro estáticas no C ++ nativo e aqui para classes estáticas no C ++ gerenciado. Você pode usar essa classe de utilitário onde quer que cole seu código.

No .NET, por exemplo, coisas como Min()e Max()são fornecidas como membros estáticos na System.Mathclasse .

Se todas as suas funções estão relacionadas à matemática e você também gostaria de ter uma Mathclasse gigantesca , convém dividi-la ainda mais e ter classes como TrigonometryUtilities, EucledianGeometryUtilitiese assim por diante.

Outra opção seria colocar a funcionalidade compartilhada em uma classe base das classes que exigem essa funcionalidade. Isso funciona bem, quando as funções nas perguntas precisam operar com dados da instância, no entanto, essa abordagem também é menos flexível se você deseja evitar herança múltipla e se ater apenas a uma classe base, porque você estaria "esgotando" sua única base classe apenas para obter acesso a algumas funcionalidades compartilhadas.


18
IMHO, classes de utilitário com nada além de membros estáticos são um anti-padrão em C ++. Você está usando uma classe para reproduzir perfeitamente o comportamento de um espaço para nome, o que realmente não faz sentido.
ipeet

+1 por mencionar classes de utilidade. Idiomas como C # exigem que tudo esteja em uma classe, por isso é bastante comum criar várias classes de utilitários para vários propósitos. A implementação dessas classes como estáticas torna os utilitários ainda mais amigáveis ​​e evita as dores de cabeça que a herança às vezes pode criar, principalmente quando as classes base estão ficando inchadas com código que pode ser usado apenas por um ou dois descendentes. Técnicas semelhantes podem ser aplicadas em outros idiomas para fornecer um contexto significativo para suas funções utilitárias, em vez de deixá-las flutuando no escopo global.
31412 S.Robins

5
@ S.Robins: Não há necessidade disso em C ++, basta colocá-los em um espaço para nome, que tem exatamente o mesmo efeito.
DeadMG

0

Desambique o termo "função auxiliar". Uma definição é uma função de conveniência que você usa o tempo todo apenas para realizar algum trabalho. Eles podem viver no namespace principal e ter seus próprios cabeçalhos, etc. A outra definição de função auxiliar é uma função de utilitário para uma única classe ou família de classes.

// a general helper 
template <class T>
bool isPrinter(T& p){
   return (dynamic_cast<Printer>(p))? true: false;
}

    // specific helper for printers
namespace printer_utils {    
  namespace HP {
     print_alignment_page() { printAlignPage();}
  }

  namespace Xerox {
     print_alignment_page() { Alignment_Page_Print();}
  }

  namespace Canon {
     print_alignment_page() { AlignPage();}
  }

   namespace Kyocera {
     print_alignment_page() { Align(137,4);}
   }

   namespace Panasonic {
      print_alignment_page() { exec(0xFF03); }
   }
} //namespace

Agora isPrinterestá disponível para qualquer código, incluindo seu cabeçalho, mas print_alignment_pagerequer uma using namespace printer_utils::Xerox;diretiva. Pode-se também referi-lo como

Canon::print_alignment_page();

para ser mais claro.

O C ++ STL possui o std::namespace que cobre quase todas as suas classes e funções, mas os divide categoricamente em mais de 17 cabeçalhos diferentes, para permitir que o codificador obtenha os nomes das classes, nomes de funções etc. fora do caminho, se quiserem escrever próprios.

De fato, NÃO é recomendável usar using namespace std;em um arquivo de cabeçalho ou, como costuma ser feito, como a primeira linha dentro main(). std::tem 5 letras e muitas vezes parece uma tarefa anterior à função que se deseja usar (especialmente std::coute std::endl!), mas serve a um propósito.

O novo C ++ 11 possui alguns sub namespaces para serviços especiais, como

std::placeholders,
std::string_literals,
std::chrono,
std::this_thread,
std::regex_constants

que pode ser trazido para uso.

Uma técnica útil é a composição do espaço para nome . Um define um espaço para nome personalizado para armazenar os espaços para nome que você precisa para seu .cpparquivo específico e usa-o em vez de várias usinginstruções para cada coisa em um espaço para nome que você possa precisar.

#include <iostream>
#include <string>
#include <vector>

namespace Needed {
  using std::vector;
  using std::string;
  using std::cout;
  using std::endl;
}

int main(int argc, char* argv[])
{
  /*  using namespace std; */
      // would avoid all these individual using clauses,
      // but this way only these are included in the global
      // namespace.

 using namespace Needed;  // pulls in the composition

 vector<string> str_vec;

 string s("Now I have the namespace(s) I need,");

 string t("But not the ones I don't.");

 str_vec.push_back(s);
 str_vec.push_back(t);

 cout << s << "\n" << t << endl;
 // ...

Essa técnica limita a exposição ao todo std:: namespace( é grande! ) E permite escrever código mais limpo para as linhas de código mais comuns que as pessoas escrevem com mais frequência.


-2

Você pode colocá-lo em uma função de modelo para disponibilizá-lo para diferentes tipos de números inteiros e / ou flutuantes:

template <typename T>
T math_function1(T){
 ..
}

Você também pode criar tipos personalizados simples que representam, por exemplo, grandes números ou números complexos, sobrecarregando os operadores relevantes para o seu tipo personalizado, para torná-los compatíveis com os modelos.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.