Domanda

Sfondo

Ho una classe container che usa vector < std :: string > internamente. Ho fornito un metodo AddChar (std :: string) a questa classe wrapper che fa un push_back () al vettore interno. Nel mio codice, devo aggiungere più elementi al contenitore qualche volta. Per questo devo usare

container.AddChar("First");
container.AddChar("Second");

Questo ingrandisce il codice. Quindi, per renderlo più semplice, ho intenzione di sovraccaricare l'operatore < < ;. In modo che io possa scrivere

container << "First" << "Second"

e due elementi verranno aggiunti al vettore sottostante.

Ecco il codice che ho usato per quello

class ExtendedVector
{
private:
    vector<string> container;

public:
    friend ExtendedVector& operator<<(ExtendedVector& cont,const std::string str){
        cont.AddChar(str);
        return cont;
    }

    void AddChar(const std::string str)
    {
        container.push_back(str);
    }

    string ToString()
    {
        string output;
        vector<string>::iterator it = container.begin();
        while(it != container.end())
        {
            output += *it;
            ++it;
        }
        return output;
    }
};

Funziona come previsto.

Domande

  1. Il sovraccarico dell'operatore è stato scritto correttamente?
  2. È una buona pratica sovraccaricare gli operatori in situazioni come questa?
  3. Ci saranno problemi di prestazioni o altri problemi con questo codice?

Qualche idea?

Modifica

Dopo aver ascoltato gli eccellenti commenti, ho deciso di non sovraccaricare < < poiché non ha senso qui. Ho rimosso il codice di sovraccarico dell'operatore ed ecco il codice finale.

class ExtendedVector
{
private:
    vector<string> container;

public:

    ExtendedVector& AddChar(const std::string str)
    {
        container.push_back(str);
        return *this;
    }

         .. other methods
}

Questo mi permette di aggiungere

container.AddChar("First").AddChar("Second")

In C #, posso farlo più facilmente usando la parola chiave params. Il codice sarà come

void AddChar(params string[] str)
{
    foreach(string s in str)
       // add to the underlying collection
}

Lo so in C ++, possiamo usare ... per specificare la lunghezza variabile dei parametri. Ma AFAIK, non è sicuro. Quindi è una pratica raccomandata farlo? In modo che io possa scrivere

container.AddChar("First","Second")

Grazie per le risposte.

È stato utile?

Soluzione

  

Il sovraccarico dell'operatore è stato scritto correttamente?

Lo è, ma si può fare di meglio. Come qualcun altro menzionato, la tua funzione può essere definita interamente da funzioni pubbliche esistenti. Perché non farlo usare solo quelli? In questo momento, è un amico, il che significa che appartiene ai dettagli di implementazione. Lo stesso vale se inserisci l'operatore < < come membro della tua classe. Tuttavia, rendi il tuo operatore < < una funzione non membro , non amico .

class ExtendedVector {
    ...
};

// note, now it is *entirely decoupled* from any private members!
ExtendedVector& operator<<(ExtendedVector& cont, const std::string& str){
    cont.AddChar(str);
    return cont;
}

Se cambi classe, non sarai sicuro che il tuo operatore < < funzionerà ancora. Ma se il tuo operatore < < dipende interamente solo dalle funzioni pubbliche, quindi puoi essere sicuro che funzionerà solo dopo aver apportato modifiche ai dettagli di implementazione della tua classe. Yay!

  

È buona norma sovraccaricare gli operatori in situazioni come questa?

Come ha detto ancora un altro ragazzo, questo è discutibile. In molte situazioni, il sovraccarico dell'operatore sembrerà "pulito". a prima vista, ma sembrerà un inferno il prossimo anno, perché non hai più idea di cosa avevi in ??mente quando dai ad alcuni simboli un amore speciale. Nel caso dell'operatore < < ;, penso che sia un uso corretto. Il suo uso come operatore di inserimento per flussi è ben noto. E conosco le applicazioni Qt e KDE che lo usano ampiamente in casi come

QStringList items; 
items << "item1" << "item2";

Un caso simile è boost.format che riutilizza anche operator% per passare argomenti per segnaposto nella sua stringa:

format("hello %1%, i'm %2% y'old") % "benny" % 21

Naturalmente è anche possibile usarlo lì. Ma il suo uso per il formato printf specifica è ben noto e quindi anche il suo uso è OK, imho. Ma come sempre, anche lo stile è soggettivo, quindi prendilo con un granello di sale :)

  

Come posso accettare argomenti a lunghezza variabile in un modo typesafe?

Bene, c'è un modo per accettare un vettore se stai cercando argomenti omogenei:

void AddChars(std::vector<std::string> const& v) {
    std::vector<std::string>::const_iterator cit =
        v.begin();
    for(;cit != v.begin(); ++cit) {
        AddChar(*cit);
    }
}

Tuttavia non è davvero comodo passarlo. Devi costruire il tuo vettore manualmente e poi passare ... Vedo che hai già la giusta sensazione riguardo alle funzioni dello stile vararg. Non si dovrebbero usare per questo tipo di codice e solo quando si interfaccia con il codice C o funzioni di debug. Un altro modo di gestire questo caso è applicare la programmazione del preprocessore. Questo è un argomento avanzato ed è piuttosto confuso. L'idea è di generare automaticamente sovraccarichi fino a un limite superiore approssimativamente in questo modo:

#define GEN_OVERLOAD(X) \
void AddChars(GEN_ARGS(X, std::string arg)) { \
    /* now access arg0 ... arg(X-1) */ \
    /* AddChar(arg0); ... AddChar(arg(N-1)); */ \
    GEN_PRINT_ARG1(X, AddChar, arg) \
}

/* call macro with 0, 1, ..., 9 as argument
GEN_PRINT(10, GEN_OVERLOAD)

Questo è lo pseudo codice. Puoi dare un'occhiata alla libreria di preprocessore boost qui .

La prossima versione di C ++ offrirà possibilità molto migliori. Gli elenchi di inizializzatori possono essere utilizzati:

void AddChars(initializer_list<std::string> ilist) {
    // range based for loop
    for(std::string const& s : ilist) {
        AddChar(s);
    }
}

...
AddChars({"hello", "you", "this is fun"});

È anche possibile nel prossimo C ++ supportare molti argomenti (di tipo misto) arbitrari usando modelli variadici . GCC4.4 avrà il supporto per loro. GCC 4.3 li supporta già parzialmente.

Altri suggerimenti

1) Sì, a parte il fatto che AddChar è pubblico non c'è motivo per cui debba essere un amico .

2) Questo è discutibile. < < è un po 'nella posizione di essere l'operatore il cui sovraccarico per "strano" le cose sono almeno accettate a malincuore.

3) Niente di ovvio. Come sempre, la profilazione è tua amica. Puoi considerare di passare i parametri stringa a AddChar e operator < < per riferimento const ( const std :: string & amp; ) per evitare copia non necessaria.

  

È buona norma sovraccaricare   operatori in situazioni come questa?

Non credo. È confuso da morire per qualcuno che non sa di aver sovraccaricato l'operatore. Basta attenersi ai nomi dei metodi descrittivi e dimenticare i caratteri extra che stai digitando, non ne vale la pena. Il tuo manutentore (o tu stesso tra 6 mesi) ti ringrazierà.

Preferirei non sovraccaricarlo in questo modo personalmente perché i vettori normalmente non hanno un operatore di turno sinistro sovraccarico - non è proprio il linguaggio ;-)

Probabilmente restituirei un riferimento da AddChar invece in questo modo:

ExtendedVector& AddChar(const std::string& str) {
    container.push_back(str);
    return *this;
}

in modo da poterlo fare

container.AddChar("First").AddChar("Second");

che non è molto più grande degli operatori bitshift.

(vedi anche il commento di Logan sul passaggio delle stringhe per riferimento anziché per valore).

Il sovraccarico dell'operatore in questo caso non è una buona pratica in quanto rende il codice meno leggibile. Lo standard std :: vector non ha neanche per spingere elementi, per buoni motivi.

Se sei preoccupato che il codice del chiamante sia troppo lungo, potresti considerare questo invece dell'operatore sovraccarico:

container.AddChar("First").AddChar("Second");

Questo sarà possibile se hai AddChar () restituisci * this .

È divertente avere questa funzione toString () . In quel caso, un operatore < < per l'output in uno stream sarebbe invece la cosa standard da usare! Quindi, se si desidera utilizzare gli operatori, rendere la funzione toString () un operatore < < .

L'operatore non è sovraccaricato correttamente qui. Non c'è motivo di rendere l'operatore un amico poiché può essere un membro della classe. Amico è per le funzioni che non sono membri effettivi della classe (come quando si sovraccarica < < per ostream in modo che l'oggetto possa essere emesso in cout o in stream).

Quello che vuoi veramente essere l'operatore:

ExtendedVector& operator<<(const std::string str){
    AddChar(str);
    return *this;
}

Di solito è considerata una cattiva pratica sovraccaricare gli operatori in un modo che li fa fare qualcosa di come fanno normalmente. & Lt; < è normalmente il bit shift, quindi sovraccaricarlo in questo modo può essere fonte di confusione. Ovviamente sovraccarichi STL < < per "quotazione di flusso" e così con ciò potrebbe avere senso sovraccaricarlo per il tuo uso in modo simile. Ma questo non sembra quello che stai facendo, quindi probabilmente vorrai evitarlo.

Non ci sono problemi di prestazioni poiché il sovraccarico dell'operatore è lo stesso di una normale chiamata di funzione, solo la chiamata viene nascosta perché viene eseguita automaticamente dal compilatore.

Questo renderà le cose piuttosto confuse, userei la stessa sintassi di std :: cin in una variabile:

std :: cin > > someInt;

" First " & Gt; > contenitore;

In questo modo è almeno un operatore di inserimento. Per me quando qualcosa ha un < < operatore sovraccarico mi aspetto che stia emettendo qualcosa. Proprio come std :: cout.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top