Semplificazione del passaggio ottimale dei parametri di C ++ 11 quando è necessaria una copia

4

Mi sembra che in C ++ 11 sia stata prestata molta attenzione a semplificare i valori di ritorno da funzioni e metodi, ovvero: con la semantica del movimento è possibile restituire semplicemente la copia pesante ma valori poco costosi (mentre in C ++ 98/03 la linea guida generale era di usare i parametri di output tramite riferimenti o puntatori non const), ad esempio:

// C++11 style
vector<string> MakeAVeryBigStringList();

// C++98/03 style
void MakeAVeryBigStringList(vector<string>& result);

D'altra parte, mi sembra che si debba lavorare di più sul passaggio dei parametri di input, in particolare quando è necessaria una copia di un parametro di input , ad es. in costruttori e setter.

La mia comprensione è che la tecnica migliore in questo caso è utilizzare modelli e std::forward<> , ad es. (seguendo lo schema di questa risposta su C ++ 11 passaggio parametri ottimale ):

class Person
{
    std::string m_name;

public:   
    template <class T,
              class = typename std::enable_if
              <
                  std::is_constructible<std::string, T>::value
              >::type>
    explicit Person(T&& name) 
        : m_name(std::forward<T>(name)) 
    {
    }

    ...
};

Un codice simile potrebbe essere scritto per setter.

Francamente, questo codice sembra standardplice e complesso e non si adatta bene quando ci sono più parametri (ad es. se un attributo cognome viene aggiunto alla classe sopra).

Sarebbe possibile aggiungere una nuova funzionalità a C ++ 11 al codice semplificare come questo (proprio come lambdas semplificare il codice C ++ 98/03 con i funtori in diversi casi)?

Stavo pensando ad una sintassi con qualche carattere speciale, come @ (poiché l'introduzione di una &&& in aggiunta a && sarebbe una digitazione eccessiva :) per esempio:.

class Person
{
    std::string m_name;

public:   
    /*
       Simplified syntax to produce boilerplate code like this:

       template <class T,
              class = typename std::enable_if
              <
                  std::is_constructible<std::string, T>::value
              >::type>
    */
    explicit Person(std::string@ name) 
        : m_name(name) // implicit std::forward as well 
    {
    }

    ...
};

Ciò sarebbe molto conveniente anche per i casi più complessi che coinvolgono più parametri, ad es.

Person(std::string@ name, std::string@ surname)
    : m_name(name),
      m_surname(surname)
{
}

Sarebbe possibile aggiungere una sintassi comoda semplificata come questa in C ++? Quali sarebbero gli aspetti negativi di una tale sintassi?

    
posta Mr.C64 30.10.2012 - 19:00
fonte

2 risposte

2

Purtroppo, no, perché ci sono troppi casi. Nell'esempio, si usa std::string@ per rappresentare il tipo perfettamente inoltrato di un oggetto che deve essere perfettamente inoltrato a un costruttore di std::string , e dire "Un codice simile potrebbe essere scritto per setter.". Ma ti sbagli. Avresti bisogno di un'altra sintassi separata per l'assegnazione. Ad esempio, posso costruire std::vector<anything> da int , ma non posso assegnare un int a std::vector<anything> . Quindi mi piacerebbe come std::vector<anything># per i compiti. E che dire dell'operatore + ? Se voglio perfezionare l'inoltro di un RHS a operator+ di un membro, allora avrei bisogno anche di una notazione. E non può essere un simbolo esistente come + o che renderebbe C ++ molto più difficile da analizzare di quello che è già! Quindi puoi vedere che questo non si applica universalmente come sembri pensare lo fa.

In secondo luogo, non sono d'accordo sul fatto che la piastra di riscaldamento esistente non scala bene. Scala in modo lineare, il che è abbastanza buono, penso. (Si noti che i membri e il boilerplate mem-init-list sono richiesti in ogni caso e quindi non fanno parte del ridimensionamento. Anche se lo fosse, è ancora lineare)

class Person
{
    std::string m_name;
    std::string m_address;
    std::string m_nickname;
    std::string m_phonenumber;
    std::string m_comment;

public:   
    template <class T, class U, class V, class W, class X,
              class = typename std::enable_if <
                  std::is_constructible<std::string, T>::value &&
                  std::is_constructible<std::string, U>::value &&
                  std::is_constructible<std::string, V>::value &&
                  std::is_constructible<std::string, W>::value &&
                  std::is_constructible<std::string, X>::value
              >::type>
    explicit Person(T&& name, U&& addr, V&& nick, W&& phone, X&& comment) 
        : m_name(std::forward<T>(name)), 
          m_address(std::forward<T>(addr)),
          m_nickname(std::forward<T>(nick)),
          m_phonenumber(std::forward<T>(phone)),
          m_comment(std::forward<T>(comment)),
    {
    }

    ...
};

Terzo: questo è necessario solo quando è necessario passare perfettamente un tipo sconosciuto al membro, che è molto raro . Normalmente, prendi tutti i membri come std::string in base al valore e spostali nei membri, il che è incredibilmente vicino a quello ottimale considerando quanto sia incredibilmente facile.

    
risposta data 30.10.2012 - 19:41
fonte
3

Frankly, this code seems boilerplate and complex

Probabilmente perché è completamente sbagliato. La parte "Per valore" di quella risposta era ciò che dovevi fare. L'altra parte, nessuno lo fa mai. L'unica ragione per farlo era che sapevi in anticipo che il tuo membro dei dati non era mobile (piuttosto raro) e non-copiabile e non sapevi cosa poteva essere costruito da una combinazione estremamente rara di circostanze. Non copre nemmeno quella circostanza, dal momento che dovrebbe essere variadica.

class Person {
    std::string name;
public:
    Person(std::string cname)
        : name(std::move(cname)) {}
};

Fatto.

    
risposta data 30.10.2012 - 19:39
fonte

Leggi altre domande sui tag