2016-05-11 8 views
14

Ich habe die folgende Klasse:Sollte ich() oder {} beim Weiterleiten von Argumenten verwenden?

struct foo 
{ 
    std::size_t _size; 
    int* data; 
public: 
    explicit foo(std::size_t s) : _size(s) { } 
    foo(std::size_t s, int v) 
     : _size(s) 
    { 
     data = new int[_size]; 
     std::fill(&data[0], &data[0] + _size, v); 
    } 

    foo(std::initializer_list<int> d) 
     : _size(d.size()) 
    { 
     data = new int[_size]; 
     std::copy(d.begin(), d.end(), &data[0]); 
    } 

    ~foo() { delete[] data; } 

    std::size_t size() const { return _size; } 
}; 

Und ich möchte Argumente es wie folgt zu übermitteln:

template <typename... Args> 
auto forward_args(Args&&... args) 
{ 
    return foo{std::forward<Args>(args)...}.size(); 
    //--------^---------------------------^ 
} 

std::cout << forward_args(1, 2) << " " << forward_args(1) << " " 
      << forward_args(2) << "\n"; 

Wenn ich ersetzen {} mit () der Ausgang 1 1 2 ist statt 2 1 1.

Was wäre für meine Klasse am sinnvollsten?

+0

"Was wäre für meine Klasse am sinnvollsten?" Dies hängt davon ab, was Ihre Klasse tun soll. '{}' bevorzugt 'std :: initialiser_list','() 'nicht. Von dort kommt der Unterschied. Nur * Sie * wissen, wie die Klasse sich verhalten soll und was sie daher tun soll. – Angew

+0

Sie sollten vermeiden, dass diese Contractors gleichzeitig sind. Sie können ein Tag hinzufügen, um explizit zu sein: 'foo (size_with_value_tag, std :: size_t s, int v)'. – Jarod42

Antwort

2

Die Verwendung von {} vs. () bestimmt, welcher Konstruktor aufgerufen wird.

  • Die {} wird das Formular aufrufen foo(std::initializer_list<int> d) und Sie erhalten die Ergebnisse, die Sie zu erwarten scheinen.

  • Die () rufen die explicit foo(std::size_t s) und foo(std::size_t s, int v), und in allen Fällen das erste Element ist die Größe, die Argumente so gegeben, Sie erhalten die Ergebnisse, die Sie sehen.

Welche zu bevorzugen bilden, hängt von der Semantik Sie die forward_args Methode unterstützen wollen. Wenn Sie möchten, dass die Parameter "so wie sie sind" übergeben werden, sollte () verwendet werden (in diesem Fall muss der Benutzer zuerst ein initialiser_list als Argument angeben).

Möglicherweise (wahrscheinlich) das Formular, das Sie bevorzugen möchten, ist () und wird wie folgt verwendet;

template <typename... Args> 
auto forward_args(Args&&... args) 
{ 
    return foo(std::forward<Args>(args)...).size(); 
    //--------^---------------------------^ 
} 

int main() 
{ 
    std::cout << forward_args(std::initializer_list<int>{1, 2}) << " " 
       << forward_args(std::initializer_list<int>{3}) << " " 
       << forward_args(std::initializer_list<int>{4}) << "\n"; 
} 

Exkurs; Die cppreference page auf der initializer_list hat ein gutes Beispiel für dieses Verhalten.

Wenn gewünscht (d.h. forward_args({1,2}) zu unterstützen), Sie eine Überlastung für initializer_list liefern kann;

template <class Arg> 
auto forward_args(std::initializer_list<Arg> arg) 
{ 
    return foo(std::move(arg)).size(); 
} 

Wie bereits erwähnt: Da der initializer_list sind die Klassenkonstruktoren letztlich die Quelle der Verwirrung, und dies zeigt sich bei der Objektkonstruktion. Es gibt einen Unterschied zwischen foo(1,2) und foo{1,2}; Letzterer ruft die initializer_list Form des Konstruktors auf. Eine Technik, um dies aufzulösen, besteht darin, ein "Tag" zu verwenden, um die "normalen" Konstruktorformen von der initializer_list Form zu unterscheiden.

+1

Der Fall einer Klasse ohne Ctors wird von '{}' unterstützt. – Yakk

+0

@Yakk, wahr. 'std :: make_unique' leidet darunter; http://coliru.stacked-crooked.com/a/7a95e11d3238ea9c – Niall

2

In diesem Fall, da die Klasse hat einen Konstruktor, der ein std::initializer_list nimmt, wird {} in der Fabrik-Funktion oft die semantische Bedeutung eines Argumentliste Sie ändern geben - mit einer Packung von Argumenten mehr als 2 in eine Dreh initialisierer_liste.

Dies wird für Benutzer der Funktion überraschend sein.

Verwenden Sie daher das Formular ().Benutzer, die eine initializer_list kann passieren wollen so explizit von

forward_args({ ... }); 

NB für die obige Syntax Aufruf arbeiten Sie eine weitere Überlastung zur Verfügung stellen müssen:

template <class T> 
auto forward_args(std::initializer_list<T> li) 
{ 
    return foo(li).size(); 
} 
+2

* "may, um so explizit durch den Aufruf von" *, Sie wahrscheinlich bedeuten "forward_args (std :: initializer_list {...});', sonst hat es gewonnen nicht abgeleitet werden –

+0

Sinn ergibt. Alle Standard-Bibliotheken, die ich ausprobiert habe, verwenden '' '', um in 'std :: make_unique' weiterzuleiten. – user6319548

+0

@PiotrSkotnicki guten Fang. es braucht noch eine Überladung. ty –

1

Wirklich, sollten Sie nicht sein Definieren Sie Ihre Klasse foo so (vorausgesetzt, es liegt in Ihrer Kontrolle). Die beiden Konstruktoren haben eine Überlappung und führen zu einer schlechten Situation, in der die zwei Initialisierungen:

foo f1(3); 
foo f2{3}; 

bedeuten zwei verschiedene Dinge; und Leute, die Weiterleitungsfunktionen schreiben müssen, stehen vor unlösbaren Problemen. Dies wird ausführlich in this blog post behandelt. Auch std::vector leidet unter dem gleichen Problem.

Stattdessen schlage ich vor, Sie verwenden einen ‚getaggt‘ Konstruktor:

struct with_size {}; // just a tag 

struct foo 
{ 
    explicit foo(with_size, std::size_t s); 

    explicit foo(with_size, std::size_t s, int v); 

    foo(std::initializer_list<int> d); 

    // ... 
} 

Jetzt haben Sie keine Überlappung, und Sie können sicher die {} Variante verwenden.

+0

Sie können auch 'struct foo_size {std :: size_t size; } 'dann' foo (foo_size) 'nicht explizit. Zugegeben, 'foo ({1})' ist immer noch verwirrend. :) – Yakk

Verwandte Themen