Wo soll ich Funktionen ablegen, die sich nicht auf eine Klasse beziehen?

47

Ich arbeite an einem C ++ - Projekt, in dem ich eine Reihe von mathematischen Funktionen habe, die ich ursprünglich als Teil einer Klasse geschrieben habe. Als ich mehr Code geschrieben habe, wurde mir klar, dass ich diese mathematischen Funktionen überall brauche.

Wo ist der beste Ort, um sie zu platzieren? Angenommen, ich habe Folgendes:

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

Und wenn ich eine andere Klasse schreibe, kann ich das math_function1in dieser anderen Klasse nicht (oder zumindest weiß ich nicht, wie ich das machen soll) . Außerdem habe ich festgestellt, dass einige dieser Funktionen nicht wirklich mit Klasse A zusammenhängen. Sie schienen am Anfang zu stehen, aber jetzt kann ich sehen, dass es sich nur um mathematische Funktionen handelt.

Was ist in dieser Situation eine gute Praxis? Im Moment habe ich sie in die neuen Klassen kopiert, was sicher die schlechteste Praxis ist.

Kokosnuss
quelle
11
Hast du etwas über das staticKeyword erfahren ?
S.Lott,
30
In C ++ werden freie Funktionen fast immer den Memberfunktionen vorgezogen.
Pubby
4
Es gibt keine Regel, die besagt, dass alles in einer Klasse sein muss. Zumindest nicht in C ++.
tdammers
2
Ich würde einen Namespace einer Klasse mit einer Reihe statischer Methoden vorziehen
Nick Keighley

Antworten:

70

C ++ kann auch Funktionen enthalten, die keine Methoden sind. Wenn sie nicht zu einer Klasse gehören, platzieren Sie sie nicht in einer Klasse, sondern nur in einem globalen oder anderen Namespace-Bereich

namespace special_math_functions //optional
{
    int math_function1(int arg)
    {
         //definition 
    }
}
jk.
quelle
6
+1 Dies ist die sinnvollste Lösung, obwohl der zusätzliche Namespace nicht erforderlich zu sein scheint.
Pubby
1
nein es ist nicht nötig
jk.
27
Einige Namespaces sind nützlich, um potenzielle Namenskollisionen mit anderen Bibliotheken zu reduzieren.
Bill Door
11
Die Verwendung eines Namespace ist auch deshalb sinnvoll, weil dadurch eindeutig festgelegt wird, ob es sich bei dem Aufruf um eine Methode oder eine Funktion handelt. ( math_function1(42)ruft möglicherweise ein Mitglied der aktuellen Klasse auf; special_math_functions::math_function1(42)ruft eindeutig eine unabhängige Funktion auf). Davon abgesehen ::math_function(42)liefert dies die gleiche Disambiguierung.
Ipeet
2
Namensräume sind nicht erforderlich, aber auch nicht verboten. Daher warum sagt diese Antwort // optional. Nach Geschmack würzen.
user253751
6

Hängt davon ab, wie das Projekt organisiert ist und welche Art von Entwurfsmustern Sie verwenden, vorausgesetzt, es handelt sich ausschließlich um Utility-Code. Sie haben die folgenden Optionen:

  • Wenn Sie Objekte nicht für alles verwenden müssen, können Sie sie einfach in eine Datei einfügen, ohne dass ein Klassenwrapper um sie herum vorhanden ist. Dies kann mit oder ohne Namespace erfolgen, obwohl der Namespace empfohlen wird, um zukünftige Probleme zu vermeiden.
  • Für verwaltetes C ++ können Sie eine statische Klasse erstellen , die sie alle enthält. Dies funktioniert jedoch nicht wirklich wie eine tatsächliche Klasse, und ich verstehe, dass es sich um ein C ++ - Anti-Pattern handelt.
  • Wenn Sie kein verwaltetes C ++ verwenden, können Sie einfach statische Funktionen verwenden , um auf sie zuzugreifen und sie alle in einer einzigen Klasse zu haben. Dies kann nützlich sein, wenn es auch andere Funktionen gibt, für die Sie ein ordnungsgemäß instanziiertes Objekt wünschen, das ebenfalls ein Anti-Pattern sein kann.
  • Wenn Sie sicherstellen möchten, dass nur eine Instanz des Objekts mit den Funktionen vorhanden ist, können Sie das Singleton-Muster für eine Dienstprogrammklasse verwenden, die Ihnen auch in Zukunft eine gewisse Flexibilität bietet, da Sie jetzt Zugriff auf nicht statische Attribute haben. Dies wird von begrenztem Nutzen sein und gilt nur dann, wenn Sie aus irgendeinem Grund ein Objekt benötigen. Wenn Sie dies tun, werden Sie wahrscheinlich bereits wissen, warum.

Beachten Sie, dass die erste Option die beste Option sein wird und die folgenden drei von begrenztem Nutzen sind. Das heißt, Sie könnten dann nur darauf stoßen, dass C # - oder Java-Programmierer etwas C ++ arbeiten oder dass Sie jemals an C # - oder Java-Code arbeiten, bei dem die Verwendung von Klassen obligatorisch ist.

rjzii
quelle
Warum runter stimmen?
rjzii
10
Ich bin nicht der Abwähler, aber wahrscheinlich, weil Sie eine Klasse mit statischen Funktionen oder einem Singleton empfehlen, während freie Funktionen in diesem Fall wahrscheinlich in Ordnung wären (und für viele Dinge in C ++ akzeptabel und nützlich sind).
Anton Golov
@AntonGolov - Freie Funktionen sind das erste, was ich in der Liste erwähnt habe. :) Der Rest von ihnen sind eher OOP-orientierte Ansätze für Situationen, in denen es um "Alles muss eine Klasse sein!" Geht. Umgebungen.
rjzii
9
@Rob Z: C ++ gehört jedoch nicht zu den "Alles muss eine Klasse sein!" Umgebungen.
David Thornley
1
Seit wann ist es OOP, reine Funktionen in eine Klasse zu zwingen? Scheint eher ein OOP-Cargo-Kult zu sein.
Deduplicator
1

Wie Sie bereits sagten, ist das Kopieren und Einfügen des Codes die schlechteste Form der Wiederverwendung von Code. Wenn Sie Funktionen haben, die keiner Ihrer Klassen angehören oder in mehreren Szenarien verwendet werden können, ist eine Helfer- oder Utility-Klasse der beste Ort, um sie zu platzieren. Wenn sie keine Instanzdaten verwenden, können sie statisch gemacht werden, sodass Sie keine Instanz der Utility-Klasse erstellen müssen, um sie zu verwenden.

Sehen Sie hier für eine Diskussion der statischen Member - Funktionen in native C ++ und hier für statische Klassen in Managed C ++. Sie können diese Dienstprogrammklasse dann überall dort verwenden, wo Sie Ihren Code eingefügt hätten.

In .NET werden Dinge wie Min()und Max()als statische Member für die System.MathKlasse bereitgestellt .

Wenn alle Funktionen Mathe bezogen sind und Sie würden otherwiese eine gigantische haben MathKlasse, könnten Sie es wollen weiter brechen und haben Klassen wie TrigonometryUtilities, EucledianGeometryUtilitiesund so weiter.

Eine andere Option wäre, geteilte Funktionalität in eine Basisklasse der Klassen zu setzen, die diese Funktionalität benötigen. Dies funktioniert gut, wenn die Funktionen in Fragen Instanzdaten verarbeiten müssen. Dieser Ansatz ist jedoch auch weniger flexibel, wenn Sie die Mehrfachvererbung vermeiden und sich nur an eine Basisklasse halten möchten, da Sie Ihre eine Basis "verbrauchen" würden Klasse, nur um Zugriff auf einige gemeinsame Funktionen zu erhalten.

PersonalNexus
quelle
18
IMHO, Utility-Klassen mit nichts als statischen Elementen sind in C ++ ein Anti-Pattern. Sie verwenden eine Klasse, um das Verhalten eines Namespaces perfekt zu reproduzieren, was wirklich keinen Sinn ergibt.
Ipeet
+1 für die Erwähnung von Utility-Klassen. Sprachen wie C # erfordern, dass sich alles in einer Klasse befindet. Daher ist es durchaus üblich, eine Reihe von Dienstprogrammklassen für verschiedene Zwecke zu erstellen. Das Implementieren dieser Klassen als Static macht die Dienstprogramme noch benutzerfreundlicher und vermeidet die Kopfschmerzen, die durch die Vererbung entstehen können, insbesondere wenn die Basisklassen mit Code überfüllt sind, der möglicherweise nur von einem oder zwei Nachkommen verwendet wird. Ähnliche Techniken können in anderen Sprachen angewendet werden, um einen aussagekräftigen Kontext für Ihre Dienstprogrammfunktionen bereitzustellen, anstatt sie im globalen Geltungsbereich schweben zu lassen.
S.Robins
5
@ S.Robins: In C ++ ist so etwas nicht erforderlich. Sie können sie einfach in einen Namespace einfügen, was genau den gleichen Effekt hat.
DeadMG
0

Disambiguieren Sie den Begriff "Hilfsfunktion". Eine Definition ist eine Komfortfunktion, die Sie ständig verwenden, um bestimmte Aufgaben zu erledigen. Diese können im Hauptnamensraum leben und ihre eigenen Header usw. haben. Die andere Helferfunktionsdefinition ist eine Dienstprogrammfunktion für eine einzelne Klasse oder Klassenfamilie.

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

Now isPrinterist für jeden Code einschließlich seines Headers verfügbar, print_alignment_pageerfordert jedoch eine using namespace printer_utils::Xerox;Direktive. Man kann es auch als bezeichnen

Canon::print_alignment_page();

klarer zu sein.

Die C ++ STL verfügt über den std::Namespace, der fast alle Klassen und Funktionen abdeckt, teilt sie jedoch kategorisch in über 17 verschiedene Header auf, damit der Codierer die Klassennamen, Funktionsnamen usw. aus dem Weg räumen kann, wenn er schreiben möchte ihre eigenen.

Tatsächlich wird NICHT empfohlen, sie using namespace std;in einer Header-Datei oder, wie so oft, als erste Zeile im Inneren zu verwenden main(). std::besteht aus 5 Buchstaben und scheint oft eine lästige Pflicht zu sein, die Funktion, die man verwenden möchte (besonders std::coutund std::endl!), vorzustellen, aber sie erfüllt einen Zweck.

Das neue C ++ 11 enthält einige Subnamespaces für spezielle Dienste wie

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

das kann zur Verwendung gebracht werden.

Eine nützliche Technik ist die Namespace-Komposition . Einer definiert einen benutzerdefinierten Namespace, der die Namespaces enthält, die Sie für Ihre bestimmte .cppDatei benötigen, und verwendet diese anstelle einer Reihe von usingAnweisungen für jedes Element in einem Namespace, das Sie möglicherweise benötigen.

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

Diese Technik begrenzt die Exposition gegenüber dem Ganzen std:: namespace( sie ist groß! ) Und ermöglicht es, saubereren Code für die häufigsten Codezeilen zu schreiben, die am häufigsten geschrieben werden.

Chris Reid
quelle
-2

Möglicherweise möchten Sie es in eine Vorlagenfunktion einfügen, um es für verschiedene Arten von Ganzzahlen und / oder Gleitkommazahlen verfügbar zu machen:

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

Sie können auch nette benutzerdefinierte Typen erstellen, die beispielsweise große oder komplexe Zahlen darstellen, indem Sie relevante Operatoren für Ihren benutzerdefinierten Typ überladen, um sie vorlagenfreundlich zu gestalten.

user1703394
quelle