Gdzie powinienem umieścić funkcje niezwiązane z klasą?

47

Pracuję nad projektem C ++, w którym mam kilka funkcji matematycznych, które początkowo napisałem, aby używać jako część klasy. Jednak kiedy pisałem więcej kodu, zdałem sobie sprawę, że wszędzie potrzebuję tych funkcji matematycznych.

Gdzie najlepiej je umieścić? Powiedzmy, że mam to:

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

A kiedy piszę inną klasę, nie mogę (lub przynajmniej nie wiem, jak to zrobić) używać tego math_function1w tej innej klasie. Ponadto zdałem sobie sprawę, że niektóre z tych funkcji nie są tak naprawdę powiązane z klasą A. Wydawało się, że były na początku, ale teraz widzę, jak są to po prostu funkcje matematyczne.

Jaka jest dobra praktyka w tej sytuacji? W tej chwili wklejam je na nowe klasy, co jest na pewno najgorszą praktyką.

orzech kokosowy
źródło
11
Czy znasz staticsłowo kluczowe?
S.Lott,
30
W C ++ prawie zawsze preferowane są funkcje wolne od funkcji składowych.
Pubby
4
Nie ma reguły, która mówi, że wszystko musi być w klasie. Przynajmniej nie w C ++.
tdammers
2
Wolę przestrzeń nazw od klasy z szeregiem metod statycznych
Nick Keighley

Odpowiedzi:

70

C ++ może dobrze działać z funkcjami nie-metodycznymi, jeśli nie należą do klasy, nie umieszczaj ich w klasie, po prostu umieść je w globalnym lub innym zakresie przestrzeni nazw

namespace special_math_functions //optional
{
    int math_function1(int arg)
    {
         //definition 
    }
}
jk.
źródło
6
+1 To najbardziej rozsądne rozwiązanie, chociaż dodatkowa przestrzeń nazw nie wydaje się konieczna.
Pubby
1
nie, to nie jest konieczne
jk.
27
Niektóre przestrzenie nazw są przydatne do ograniczenia potencjalnych kolizji nazw z innymi bibliotekami.
Bill Door
11
Korzystanie z przestrzeni nazw jest również przyjemne, ponieważ niejednoznacznie określa, czy wywołanie jest metodą, czy funkcją. ( math_function1(42)może wywoływać członka bieżącej klasy; special_math_functions::math_function1(42)wyraźnie wywołuje niezależną funkcję). To powiedziawszy, ::math_function(42)zapewnia taką samą jednoznaczność.
ipeet
2
Przestrzenie nazw nie są konieczne, ale też nie są zabronione. Stąd dlaczego ta odpowiedź mówi // optional. Dopraw do smaku.
user253751,
6

Zależy od tego, jak projekt jest zorganizowany i jakiego rodzaju wzorców projektowych używasz, zakładając, że jest to kod ściśle użyteczny, masz następujące opcje:

  • Jeśli nie musisz używać obiektów do wszystkiego, możesz zrobić coś prostego, np. Umieścić je wszystkie w pliku bez otaczania ich klasą. Może to być z przestrzenią nazw lub bez, chociaż przestrzeń nazw jest zalecana, aby zapobiec problemom w przyszłości.
  • W zarządzanym języku C ++ można utworzyć klasę statyczną, która zawiera je wszystkie; nie działa to jednak tak samo jak rzeczywista klasa i rozumiem, że jest to anty-wzorzec C ++.
  • Jeśli nie używasz zarządzanego C ++, możesz po prostu użyć funkcji statycznych, aby umożliwić ci dostęp do nich i mieć je wszystkie zawarte w jednej klasie. Może to być przydatne, jeśli istnieją również inne funkcje, dla których chciałbyś, aby odpowiedni obiekt utworzony przez instancję mógł być również anty-wzorcem.
  • Jeśli chcesz mieć pewność, że będzie istniała tylko jedna instancja obiektu zawierającego funkcje, możesz użyć Wzorca Singletona dla klasy narzędziowej, która również zapewnia pewną elastyczność w przyszłości, ponieważ masz teraz dostęp do atrybutów niestatycznych. Będzie to miało ograniczone zastosowanie i tak naprawdę ma zastosowanie tylko wtedy, gdy potrzebujesz obiektu z jakiegoś powodu. Szanse są, jeśli to zrobisz, będziesz już wiedział, dlaczego.

Zauważ, że pierwsza opcja będzie najlepszym wyborem, a kolejne trzy mają ograniczoną przydatność. To powiedziawszy, możesz jednak spotkać się z tym, że programiści C # lub Java wykonują pewne prace w C ++ lub jeśli kiedykolwiek pracujesz nad kodem C # lub Java, w których użycie klas jest obowiązkowe.

rjzii
źródło
Dlaczego głosowanie w dół?
rjzii
10
Nie jestem zwolennikiem, ale prawdopodobnie dlatego, że doradzasz klasie z funkcjami statycznymi lub singletonem, podczas gdy darmowe funkcje prawdopodobnie byłyby w tym przypadku w porządku (i są akceptowalne i przydatne dla wielu rzeczy w C ++).
Anton Golov
@AntonGolov - Darmowe funkcje to pierwsza rzecz, o której wspomniałem na liście. :) Reszta to podejście bardziej zorientowane na OOP w sytuacjach, w których masz do czynienia z „Wszystko musi być klasą!” środowiska.
rjzii
9
@Rob Z: Jednak C ++ nie należy do tych „Wszystko musi być klasą!” środowiska.
David Thornley,
1
Od kiedy to OOP wymusza czyste funkcje w klasie? Wydaje się bardziej jak kult OOP-cargo.
Deduplicator
1

Jak już wspomniano, wklejanie i kopiowanie kodu jest najgorszą formą ponownego użycia kodu. Jeśli masz funkcje, które nie należą do żadnej z twoich klas lub mogą być używane w kilku scenariuszach, najlepszym miejscem do ich umieszczenia byłaby klasa pomocnicza lub użyteczna. Jeśli nie używają żadnych danych instancji, można je ustawić na statyczne, więc nie trzeba tworzyć instancji klasy narzędzi, aby z nich korzystać.

Zobacz tutaj do dyskusji statycznych funkcji składowych w natywnym C ++ i tutaj dla klas statycznych w zarządzanym C ++. Następnie możesz użyć tej klasy narzędzi, gdziekolwiek wkleisz kod.

Na przykład w .NET rzeczy takie jak Min()i Max()są dostarczane jako elementy statyczne w System.Mathklasie .

Jeśli wszystkie funkcje są związane z matematyki, i byś otherwiese mieć gigantyczną Mathklasę, możesz rozbicie go dalej i mieć zajęcia, takie jak TrigonometryUtilities, EucledianGeometryUtilitiesi tak dalej.

Inną opcją byłoby umieszczenie funkcji współdzielonej w klasie bazowej klas wymagających tej funkcjonalności. Działa to dobrze, gdy funkcje w pytaniach muszą działać na danych instancji, jednak to podejście jest również mniej elastyczne, jeśli chcesz uniknąć wielokrotnego dziedziczenia i trzymać się tylko jednej klasy podstawowej, ponieważ „wykorzystasz” swoją jedną bazę klasa, aby uzyskać dostęp do niektórych wspólnych funkcji.

PersonalNexus
źródło
18
IMHO, klasy użytkowe zawierające wyłącznie statyczne elementy są anty-wzorcem w C ++. Używasz klasy, aby idealnie odtworzyć zachowanie przestrzeni nazw, co naprawdę nie ma sensu.
ipeet,
+1 za wzmiankę o klasach użytkowych. Języki takie jak C # wymagają, aby wszystko było w klasie, więc często tworzy się szereg klas narzędzi do różnych celów. Zaimplementowanie tych klas jako Statycznych sprawia, że ​​narzędzia są jeszcze bardziej przyjazne dla użytkownika i pozwala uniknąć bólów głowy, które może czasami powodować dziedziczenie, szczególnie gdy klasy podstawowe stają się rozdęte kodem, który może być używany tylko przez jednego lub dwóch potomków. Podobne techniki można zastosować w innych językach, aby zapewnić znaczący kontekst dla funkcji narzędziowych, zamiast pozostawić je zmienne w zakresie globalnym.
S.Robins
5
@ S.Robins: Nie ma takiej potrzeby w C ++, możesz po prostu umieścić je w przestrzeni nazw, która ma dokładnie taki sam efekt.
DeadMG
0

Ujednoznacznij pojęcie „funkcja pomocnicza”. Jedna z definicji to funkcja wygody, z której cały czas korzystasz tylko po to, aby wykonać jakąś pracę. Mogą one mieszkać w głównej przestrzeni nazw i mieć własne nagłówki itp. Druga definicja funkcji pomocniczej jest funkcją użyteczną dla pojedynczej klasy lub rodziny klas.

// 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

Teraz isPrinterjest dostępny dla każdego kodu, w tym jego nagłówka, ale print_alignment_pagewymaga using namespace printer_utils::Xerox;dyrektywy. Można go również określić jako

Canon::print_alignment_page();

żeby być bardziej zrozumiałym.

C ++ STL ma std::przestrzeń nazw, która obejmuje prawie wszystkie swoje klasy i funkcje, ale dzieli je kategorycznie na ponad 17 różnych nagłówków, aby umożliwić koderowi usunięcie nazw klas, nazw funkcji itp., Jeśli chcą pisać ich własny.

W rzeczywistości NIE zaleca się używania using namespace std;w pliku nagłówkowym lub, jak to często bywa, jako pierwszej linii w środku main(). std::składa się z 5 liter i często wydaje się uciążliwym wstępem do funkcji, której chce się używać (zwłaszcza std::couti std::endl!), ale służy to celowi.

Nowy C ++ 11 ma pewne podprzestrzenie nazw dla usług specjalnych, takich jak

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

które można przynieść do użytku.

Przydatną techniką jest kompozycja przestrzeni nazw . Jeden definiuje niestandardową przestrzeń nazw do przechowywania przestrzeni nazw potrzebnych dla konkretnego .cpppliku i używania jej zamiast zestawu usinginstrukcji dla każdej rzeczy w przestrzeni nazw, której możesz potrzebować.

#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;
 // ...

Ta technika ogranicza ekspozycję na całość std:: namespace( jest duża! ) I pozwala napisać czystszy kod dla najczęstszych linii kodu, które ludzie piszą najczęściej.

Chris Reid
źródło
-2

Możesz umieścić go w funkcji szablonu, aby udostępnić go dla różnych typów liczb całkowitych i / lub liczb zmiennoprzecinkowych:

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

Można również tworzyć zgrabne typy niestandardowe, które reprezentują na przykład liczby ogromne lub liczby zespolone, przeciążając odpowiednie operatory dla typu niestandardowego, aby uczynić je przyjaznymi szablonom.

użytkownik1703394
źródło