2016-08-08 12 views
57

In einer Funktion, die mehrere Argumente des gleichen Typs dauert, wie können wir garantieren, dass der Anrufer nicht die Bestellung durcheinander bringt?C++ - Funktion Argument Sicherheit

Zum Beispiel

void allocate_things(int num_buffers, int pages_per_buffer, int default_value ... 

und später

// uhmm.. lets see which was which uhh.. 
allocate_things(40,22,80,... 
+4

Compiler helfen Sie die meiste Zeit. Ansonsten sind es Ihre (Programmier-) Verantwortlichkeiten. –

+7

Ist das in C++ mit bestimmten Typen nicht so einfach? – alk

+0

Wenn die Parameter logisch organisiert sind (a, b.c, d, e ...), wird es leicht, sich alle in der richtigen Reihenfolge zu merken. –

Antwort

69

Eine typische Lösung besteht darin, die Parameter in eine Struktur mit benannten Feldern einzufügen.

AllocateParams p; 
p.num_buffers = 1; 
p.pages_per_buffer = 10; 
p.default_value = 93; 
allocate_things(p); 

Sie müssen natürlich keine Felder verwenden. Sie können Mitgliedsfunktionen oder was auch immer Sie möchten verwenden.

+0

Sie könnten dies tun, aber 'allocate_things' ist ein schlechter Name und' AllocateParams' ist nicht besser. Das Erzeugen von Strukturen von mehr oder weniger unverwandten Variablen wird die Dinge wahrscheinlich noch mehr verschleiern. –

+10

@FrankPuffer: Ja, einverstanden, aber das ist nicht der Code Review Stack Exchange. Wenn Sie Kommentare zum ursprünglichen Autorcode haben, gehören sie als Kommentare zu der Frage und nicht zu den Antworten. Dieses Codebeispiel soll eine bestimmte Technik und nichts anderes veranschaulichen. –

+11

@FrankPuffer: Ich denke, es ist klar, dass dies nur Platzhalter Namen sind. –

7

Sie können nicht. Aus diesem Grund wird empfohlen, möglichst wenige Funktionsargumente zu verwenden.

In Ihrem Beispiel könnten Sie separate Funktionen haben wie set_num_buffers(int num_buffers), set_pages_per_buffer(int pages_per_buffer) usw.

Sie haben sich wahrscheinlich bemerkt, dass allocate_things kein guter Name ist, weil es nicht ausdrücken, was die Funktion tatsächlich tut. Vor allem würde ich nicht erwarten, dass es einen Standardwert vorgibt.

+3

Und getrennte Verantwortlichkeiten. –

+3

und nicht Benutze magische Zahlen, hartkodierende Parameter, wie du es normalerweise tust, führt zu mehr Schmerzen als sein Wert. – Cody

+0

lmao, warum wird das abgelehnt? Ich dachte, es wäre gut, die Möglichkeit auszudrücken, dass Leute X/Y-Fragen stellen? gültig für die Darstellung von Alternativen, die eigene Vorteile haben –

30

Zwei gute Antworten, eins mehr: Ein anderer Ansatz wäre es, das Typsystem nach Möglichkeit zu nutzen und starke typedefs zu erstellen. Zum Beispiel mit boost stark typedef (http://www.boost.org/doc/libs/1_61_0/libs/serialization/doc/strong_typedef.html).

BOOST_STRONG_TYPEDEF(int , num_buffers); 
BOOST_STRONG_TYPEDEF(int , num_pages); 

void func(num_buffers b, num_pages p); 

Aufruf func mit Argumenten in der falschen Reihenfolge wäre jetzt ein Kompilierungsfehler.

Ein paar Notizen dazu. Erstens ist boosts starker typedef in seiner Vorgehensweise ziemlich veraltet; Sie können mit variadic CRTP viel schöneres tun und Makros komplett vermeiden. Zweitens führt dies natürlich zu Mehraufwand, da Sie oft explizit konvertieren müssen. Also im Allgemeinen willst du es nicht überbeanspruchen. Es ist wirklich nett für Dinge, die immer wieder in deiner Bibliothek auftauchen. Nicht so gut für Dinge, die als eine einzige auffallen. Wenn Sie zum Beispiel eine GPS-Bibliothek schreiben, sollten Sie einen starken Double-Typedef für Distanzen in Metern, einen starken Int64-Typedef für vergangene Epochen in Nanosekunden und so weiter haben.

+2

Für ganze Zahlen ist Scoped Enum eine gute Wahl. –

+0

Sie können mit diesem Ansatz einen Schritt weiter gehen, indem Sie benutzerdefinierte Literale verwenden, um den syntaktischen Mehraufwand beim Verwenden von Zolltypen beim Telefonieren zu reduzieren. – nate

+2

Sie können einen Aufruf erhalten, der aussieht wie 'allocate_things (40_buffers, 22_pages, 80 ...' und wenn Sie die Werte nicht an den richtigen Stellen setzen, erhalten Sie einen Compilerfehler. – nate

9

(Anmerkung: Beitrag wurde ursprünglich getaggt ‚C`)

C99 ermöglicht weiter eine Erweiterung @Dietrich Epp Idee: Verbindung wörtliche

struct things { 
    int num_buffers; 
    int pages_per_buffer; 
    int default_value 
}; 
allocate_things(struct things); 

// Use a compound literal 
allocate_things((struct things){.default_value=80, .num_buffers=40, .pages_per_buffer=22}); 

selbst kann die Adresse der Struktur übergeben.

allocate_things(struct things *); 

// Use a compound literal 
allocate_things(&((struct things){.default_value=80,.num_buffers=40,.pages_per_buffer=22})); 
+1

Aber das ist über C++. Welches keine zusammengesetzten Literale von C. –

+1

@underscore_d importiert Der Pfosten _was_ über C bis redigiert. (Der Pfosten macht noch Sinn in ein C-Kontext - unklar auf OP/πάντα ῥεῖ Veränderung - jetzt sehen Sie es mit dem Titel zu korrelieren) – chux

+1

Yup, sah gerade das. Fair play nach den ursprünglichen Tags. Obwohl der Titel immer widersprach. Wenn nur Leute würden markieren, was sie wirklich mean ... seufz –

30

Wenn Sie einen C++ 11-Compiler haben, könnten Sie user-defined literals in Kombination mit benutzerdefinierten Typen verwenden. Hier ist ein naiver Ansatz:

struct num_buffers_t { 
    constexpr num_buffers_t(int n) : n(n) {} // constexpr constructor requires C++14 
    int n; 
}; 

struct pages_per_buffer_t { 
    constexpr pages_per_buffer_t(int n) : n(n) {} 
    int n; 
}; 

constexpr num_buffers_t operator"" _buffers(unsigned long long int n) { 
    return num_buffers_t(n); 
} 

constexpr pages_per_buffer_t operator"" _pages_per_buffer(unsigned long long int n) { 
    return pages_per_buffer_t(n); 
} 

void allocate_things(num_buffers_t num_buffers, pages_per_buffer_t pages_per_buffer) { 
    // do stuff... 
} 

template <typename S, typename T> 
void allocate_things(S, T) = delete; // forbid calling with other types, eg. integer literals 

int main() { 
    // now we see which is which ... 
    allocate_things(40_buffers, 22_pages_per_buffer); 

    // the following does not compile (see the 'deleted' function): 
    // allocate_things(40, 22); 
    // allocate_things(40, 22_pages_per_buffer); 
    // allocate_things(22_pages_per_buffer, 40_buffers); 
} 
+4

... * oh wow *. +1; das ist sehr interessant. Aber ich weiß nicht, ob ich ein Szenario finden will oder nicht, wo ich es brauchen würde ... ;-) –

+1

Das sieht so aus, als ob es Makro-ified sein könnte. – rubenvb

+0

Was wäre, wenn 40 eine Variable anstelle eines Literals wäre? – Barry

7

Nur der Vollständigkeit halber, Sie benannten Argumente verwenden könnte, wenn Ihr Anruf wird.

void allocate_things(num_buffers=20, pages_per_buffer=40, default_value=20); 
// or equivalently 
void allocate_things(pages_per_buffer=40, default_value=20, num_buffers=20); 

jedoch mit der aktuellen C++ dies erfordert ziemlich viel Code implementiert werden (in der Header-Datei erklärt allocate_things(), die auch entsprechende externe Objekte num_buffers usw. bietet operator=, die ein einzigartiges geeignetes Objekt zurückgeben erklären muss).

---------- Beispiel arbeiten (für sergej)

#include <iostream> 

struct a_t { int x=0; a_t(int i): x(i){} }; 
struct b_t { int x=0; b_t(int i): x(i){} }; 
struct c_t { int x=0; c_t(int i): x(i){} }; 

// implement using all possible permutations of the arguments. 
// for many more argumentes better use a varidadic template. 
void func(a_t a, b_t b, c_t c) 
{ std::cout<<"a="<<a.x<<" b="<<b.x<<" c="<<c.x<<std::endl; } 
inline void func(b_t b, c_t c, a_t a) { func(a,b,c); } 
inline void func(c_t c, a_t a, b_t b) { func(a,b,c); } 
inline void func(a_t a, c_t c, b_t b) { func(a,b,c); } 
inline void func(c_t c, b_t b, a_t a) { func(a,b,c); } 
inline void func(b_t b, a_t a, c_t c) { func(a,b,c); } 

struct make_a { a_t operator=(int i) { return {i}; } } a; 
struct make_b { b_t operator=(int i) { return {i}; } } b; 
struct make_c { c_t operator=(int i) { return {i}; } } c; 

int main() 
{ 
    func(b=2, c=10, a=42); 
} 
+2

Sieht aus wie C++ 35, oder so ... +1. Würde gerne ein minimales Arbeitsbeispiel sehen. – sergej

+0

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4172.htm – sergej

+0

@sergej siehe bearbeitete Antwort – Walter

6

Gehst du wirklich QA alle Kombinationen von beliebigen ganzen Zahlen zu versuchen? Und werfen Sie alle Überprüfungen auf negative/Nullwerte usw.?

Erstellen Sie einfach zwei Aufzählungstypen für die minimale, mittlere und maximale Anzahl von Puffern und kleine mittlere und große Puffergrößen. Dann lassen Sie den Compiler die Arbeit tun und lassen Sie Ihre QA Leute einen Nachmittag starten:

allocate_things(MINIMUM_BUFFER_CONFIGURATION, LARGE_BUFFER_SIZE, 42); 

dann nur Sie eine begrenzte Anzahl von Kombinationen zu testen, und Sie werden zu 100% Abdeckung. Die Leute, die in 5 Jahren an Ihrem Code arbeiten, müssen nur wissen, was sie erreichen wollen und müssen nicht die Zahlen erraten, die sie benötigen oder welche Werte tatsächlich in diesem Bereich getestet wurden.

Es macht den Code etwas schwieriger zu erweitern, aber es klingt wie die Parameter sind für Low-Level-Performance-Tuning, so Twiddling die Werte sollten nicht als billig/trivial/nicht gründlich getestet werden. Eine Code-Überprüfung einer Änderung von allocate_something (25, 25, 25);

... zu

allocate_something (30, 80, 42);

... wird wahrscheinlich bekommen nur ein Achselzucken/abgeblasen, sondern ein Code-Review eines neuen EXTRA_LARGE_BUFFERS Enum-Wert wird alle richtigen Diskussionen über die Speichernutzung wahrscheinlich auslösen, Dokumentation, Performance-Tests usw.