Argomenti di parole chiave in stile Python in C ++: buona pratica o cattiva idea?

6

Mentre cercavo di capire l'ordine ottimale per i parametri opzionali di una funzione recentemente, mi sono imbattuto in questo post del blog e che accompagna il repo GitHub , che fornisce un'intestazione per un Pythonic kwargs - come facilità in C ++. Sebbene non abbia finito per usarlo, mi sorprendo a chiedermi se questo è un linguaggio buono o no in un linguaggio strongmente tipizzato. Avendo lavorato in Python per un po ', trovo la nozione di una funzione kwargs -like nel mio progetto molto attraente perché molti dei suoi oggetti / funzioni hanno un numero di parametri facoltativi (che non possono essere evitati, sfortunatamente), producendo lunghe liste di costruttori che differiscono per uno o due parametri e potrebbero essere resi molto più succinti / DRY-ish.

Che cosa, se non altro, è l'esperienza di altri con cose del genere? Dovrebbe essere evitato? Ci sono delle linee guida per questo? Quali sono i potenziali problemi / insidie?

    
posta Sebastian Lenartowicz 30.08.2016 - 15:36
fonte

1 risposta

11

Non ho molta familiarità con C ++ kwargs ma un paio di svantaggi mi vengono in mente dopo aver scremato la loro fonte:

  1. È una libreria di terze parti . È ovvio, ma devi ancora capire come integrarlo nel tuo progetto e aggiornare la sorgente quando viene modificato il repository originale.
  2. Richiedono la pre-dichiarazione globale di tutti gli argomenti . Il semplice esempio sul post del blog ha questa sezione che è a peso morto:

    #include "kwargs.h"
    
    // these are tags which will uniquely identify the arguments in a parameter
    // pack
    enum Keys {
      c_tag,
      d_tag
    };
    
    // global symbols used as keys in list of kwargs
    kw::Key<c_tag> c_key;
    kw::Key<d_tag> d_key;
    
    // a function taking kwargs parameter pack
    template <typename... Args>
    void foo(int a, int b, Args... kwargs) {
      // first, we construct the parameter pack from the parameter pack
      kw::ParamPack<Args...> params(kwargs...);
    
      ...
    

    Non conciso quanto l'originale pitonico.

  3. Potenziale bloat binario . La tua funzione deve essere un modello variadico, quindi ogni permutazione dei parametri genererà nuovamente il codice binario. Il compilatore spesso non è in grado di vedere che si differenziano in banalità e uniscono i binari.
  4. Tempi di compilazione più lenti . Ancora una volta, la tua funzione deve essere un modello e la libreria stessa è basata su modelli. Non c'è niente di sbagliato nei template, ma i compilatori hanno bisogno di tempo per analizzarli e renderli istantanei.

C ++ offre alternative native per ottenere la funzionalità dei parametri denominati:

  1. Struct wrappers . Definisci i tuoi parametri opzionali come campi di una struttura.

    struct foo_args {
        const char* title = "";
        int year = 1900;
        float percent = 0.0;
    };
    
    void foo(int a, int b, const foo_args& args = foo_args())
    {
        printf("title: %s\nyear: %d\npercent: %.2f\n",
            args.title, args.year, args.percent);
    }
    
    int main()
    {
        foo_args args;
        args.title = "foo title";
        args.percent = 99.99;
        foo(1, 2, args);
    
        /* Note: in pure C brace initalizers could be used instead
           but then you loose custom defaults -- non-initialized
           fields are always zero.
    
           foo_args args = { .title = "foo title", .percent = 99.99 };
        */
        return 0;
    }
    
  2. Oggetti proxy . Gli argomenti sono memorizzati in una struttura temporanea che può essere modificata con setter concatenati.

    struct foo {
        // Mandatory arguments
        foo(int a, int b) : _a(a), _b(b) {}
    
        // Optional arguments
        // ('this' is returned for chaining)
        foo& title(const char* title) { _title = title; return *this; }
        foo& year(int year) { _year = year; return *this; }
        foo& percent(float percent) { _percent = percent; return *this; }
    
        // Do the actual call in the destructor.
        // (can be replaced with an explicit call() member function
        // if you're uneasy about doing the work in a destructor) 
        ~foo()
        {
            printf("title: %s\nyear: %d\npercent: %.2f\n", _title, _year, _percent);
        }
    
    private:
        int _a, _b;
        const char* _title = "";
        int _year = 1900;
        float _percent = 0.0;
    };
    
    
    int main()
    {
        // Under the hood:
        //  1. creates a proxy object
        //  2. modifies it with chained setters
        //  3. calls its destructor at the end of the statement
        foo(1, 2).title("foo title").percent(99.99);
    
        return 0;
    }
    

    Nota : lo standard può essere estratto in una macro a scapito della leggibilità:

    #define foo_optional_arg(type, name, default_value)  \
        public: foo& name(type name) { _##name = name; return *this; } \
        private: type _##name = default_value
    
    struct foo {
        foo_optional_arg(const char*, title, "");
        foo_optional_arg(int, year, 1900);
        foo_optional_arg(float, percent, 0.0);
    
        ...
    
  3. Funzioni variabili . Questo ovviamente è di tipo non sicuro e richiede la conoscenza delle promozioni di tipo per avere ragione. È, tuttavia, disponibile in puro C se C ++ non è un'opzione.

    #include <stdarg.h>
    
    // Pre-defined argument tags
    enum foo_arg { foo_title, foo_year, foo_percent, foo_end };
    
    void foo_impl(int a, int b, ...)
    {
        const char* title = "";
        int year = 1900;
        float percent = 0.0;
    
        va_list args;
        va_start(args, b);
        for (foo_arg arg = (foo_arg)va_arg(args, int); arg != foo_end;
            arg = (foo_arg)va_arg(args, int))
        {
            switch(arg)
            {
            case foo_title:  title = va_arg(args, const char*); break;
            case foo_year:  year = va_arg(args, int); break;
            case foo_percent:  percent = va_arg(args, double); break;
            }
        }
        va_end(args);
    
        printf("title: %s\nyear: %d\npercent: %.2f\n", title, year, percent);
    }
    
    // A helper macro not to forget the 'end' tag.
    #define foo(a, b, ...) foo_impl((a), (b), ##__VA_ARGS__, foo_end)
    
    int main()
    {
        foo(1, 2, foo_title, "foo title", foo_percent, 99.99);
    
        return 0;
    }
    

    Nota : in C ++ questo può essere reso sicuro dal tipo con modelli variadici. L'overhead di run-time andrà a scapito dei tempi di compilazione più lenti e del bloat binario.

  4. boost :: parametro . Ancora una libreria di terze parti, anche se una libert più consolidata di qualche oscuro repository github. Svantaggi: modello pesante.

    #include <boost/parameter/name.hpp>
    #include <boost/parameter/preprocessor.hpp>
    #include <string>
    
    BOOST_PARAMETER_NAME(foo)
    BOOST_PARAMETER_NAME(bar)
    BOOST_PARAMETER_NAME(baz)
    BOOST_PARAMETER_NAME(bonk)
    
    BOOST_PARAMETER_FUNCTION(
        (int),  // the return type of the function, the parentheses are required.
        function_with_named_parameters, // the name of the function.
        tag,  // part of the deep magic. If you use BOOST_PARAMETER_NAME you need to put "tag" here.
        (required // names and types of all required parameters, parentheses are required.
            (foo, (int)) 
            (bar, (float))
        )
        (optional // names, types, and default values of all optional parameters.
            (baz, (bool) , false)
            (bonk, (std::string), "default value")
        ) 
    )
    {
        if (baz && (bar > 1.0)) return foo;
        return bonk.size();
    }
    
    int main()
    {
        function_with_named_parameters(1, 10.0);
        function_with_named_parameters(7, _bar = 3.14);
        function_with_named_parameters( _bar = 0.0, _foo = 42);
        function_with_named_parameters( _bar = 2.5, _bonk= "Hello", _foo = 9);
        function_with_named_parameters(9, 2.5, true, "Hello");
    }
    

In una nota conclusiva, non userei questa libreria kwargs semplicemente perché ci sono un certo numero di alternative abbastanza buone in C ++ per ottenere lo stesso risultato. Personalmente opterei per 1. o 2. dall'elenco (non esaustivo) sopra.

    
risposta data 30.08.2016 - 21:50
fonte

Leggi altre domande sui tag