it-swarm.com.de

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

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 jedoch mehr Code geschrieben habe, habe ich festgestellt, dass ich diese mathematischen Funktionen überall brauche.

Wo kann man sie am besten unterbringen? Nehmen wir an, ich habe Folgendes:

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

Und wenn ich eine andere Klasse schreibe, kann ich das _ [oder zumindest weiß ich nicht wie) nicht verwenden math_function1 in dieser anderen Klasse. 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 eingefügt, was sicher die schlechteste Praxis ist.

51
coconut

C++ kann Funktionen haben, die keine Methoden sind. Wenn sie nicht zu einer Klasse gehören, fügen Sie sie nicht in eine Klasse ein, sondern in einen globalen oder anderen Namespace-Bereich

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

Hängt davon ab, wie das Projekt organisiert ist und welche Art von Entwurfsmustern Sie verwenden, vorausgesetzt, dies ist ausschließlich Dienstprogrammcode. Sie haben die folgenden Optionen:

  • Wenn Sie nicht für alles Objekte verwenden müssen, können Sie einfach alles in eine Datei einfügen, ohne dass ein Klassen-Wrapper 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 alle enthält. Dies funktioniert jedoch nicht so wie eine tatsächliche Klasse, und ich verstehe, dass es sich um ein C++ - Anti-Pattern handelt.
  • Wenn Sie verwaltetes C++ nicht 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 benötigen, das möglicherweise auch ein Anti-Pattern ist.
  • Wenn Sie sicherstellen möchten, dass nur eine Instanz des Objekts mit den Funktionen vorhanden ist, können Sie Singleton Pattern für eine Dienstprogrammklasse verwenden, die Ihnen auch in Zukunft Flexibilität bietet, da Sie jetzt Zugriff darauf haben nicht statische Attribute. Dies wird nur von begrenztem Nutzen sein und gilt nur dann wirklich, wenn Sie aus irgendeinem Grund ein Objekt benötigen. Wenn Sie dies tun, wissen Sie wahrscheinlich bereits warum.

Beachten Sie, dass die erste Option die beste Wahl ist und die folgenden drei von begrenztem Nutzen sind. Das heißt, Sie könnten dann nur aufgrund von C # oder Java Programmierer, die einige C++ - Arbeiten ausführen, oder wenn Sie jemals an C # oder Java Code, wo die Verwendung ist von Klassen ist obligatorisch.

7
rjzii

Wie Sie bereits sagten, ist das 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 Hilfs- oder Dienstprogrammklasse 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.

Siehe hier für eine Diskussion der statischen Elementfunktionen in nativem C++ und hier für statische Klassen in verwaltetem C++. Sie können diese Dienstprogrammklasse dann überall dort verwenden, wo Sie Ihren Code eingefügt hätten.

In .NET werden beispielsweise Dinge wie Min() und Max() als statische Elemente auf dem System.Math class .

Wenn alle Ihre Funktionen mathematisch sind und Sie andernfalls eine gigantische Math -Klasse haben würden, möchten Sie sie möglicherweise weiter aufschlüsseln und Klassen wie TrigonometryUtilities, EucledianGeometryUtilities und so weiter haben auf.

Eine andere Möglichkeit wäre, gemeinsam genutzte Funktionen in eine Basisklasse der Klassen aufzunehmen, die diese Funktionen benötigen. Dies funktioniert gut, wenn die fraglichen Funktionen mit Instanzdaten arbeiten müssen. Dieser Ansatz ist jedoch auch weniger flexibel, wenn Sie 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 gemeinsam genutzte Funktionen zu erhalten.

1
PersonalNexus

Disambiguieren Sie den Begriff "Hilfsfunktion". Eine Definition ist eine Komfortfunktion, die Sie ständig verwenden, um Aufgaben zu erledigen. Diese können im Hauptnamensraum leben und ihre eigenen Header usw. haben. Die andere Hilfsfunktionsdefinition 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

Jetzt ist isPrinter für jeden Code verfügbar, einschließlich seines Headers, aber print_alignment_page Erfordert eine using namespace printer_utils::Xerox; - Direktive. Man kann es auch als bezeichnen

Canon::print_alignment_page();

um klarer zu sein.

Die C++ STL verfügt über den Namespace std::, Der fast alle Klassen und Funktionen abdeckt. Er unterteilt sie jedoch kategorisch in über 17 verschiedene Header, damit der Codierer die Klassennamen, Funktionsnamen usw. aus dem herausholen kann Weg, wenn sie ihre eigenen schreiben wollen.

Tatsächlich wird NICHT empfohlen, using namespace std; In einer Header-Datei oder, wie häufig, als erste Zeile in main() zu verwenden. std:: Besteht aus 5 Buchstaben und scheint oft eine lästige Pflicht zu sein, um die Funktion vorwegzunehmen, die man verwenden möchte (insbesondere std::cout Und std::endl!), Aber es erfüllt einen Zweck.

Das neue C++ 11 enthält einige Sub-Namespaces 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 Namespace-Komposition. Man definiert einen benutzerdefinierten Namespace, der die Namespaces enthält, die Sie für Ihre bestimmte .cpp - Datei benötigen, und verwendet diese anstelle einer Reihe von using -Anweisungen 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 gesamten std:: namespace (es ist groß!) und ermöglicht es einem, saubereren Code für die häufigsten Codezeilen zu schreiben, die am häufigsten geschrieben werden.

0
Chris Reid