Initialisieren von Elementvariablen mit demselben Namen für Konstruktorargumente wie für die vom C ++ - Standard zugelassenen Elementvariablen?

76

Ich habe herausgefunden, dass es möglich ist, die Mitgliedsvariablen mit einem Konstruktorargument mit demselben Namen wie im folgenden Beispiel zu initialisieren.

#include <cstdio>
#include <vector>

class Blah {
    std::vector<int> vec;

public:
    Blah(std::vector<int> vec): vec(vec)
    {}

    void printVec() {

        for(unsigned int i=0; i<vec.size(); i++)
            printf("%i ", vec.at(i));

        printf("\n");
    }
};

int main() {

    std::vector<int> myVector(3);

    myVector.at(0) = 1;
    myVector.at(1) = 2;
    myVector.at(2) = 3;

    Blah blah(myVector);

    blah.printVec();

    return 0;
}

g ++ 4.4 mit den Argumenten -Wall -Wextra -pedanticgibt keine Warnung aus und funktioniert korrekt. Es funktioniert auch mit clang ++. Ich frage mich, was der C ++ - Standard dazu sagt. Ist es legal und garantiert, dass es immer funktioniert?

Nils
quelle
1
Gute Frage. Ich habe diesen "Stil" tatsächlich ziemlich oft benutzt. Ich habe nie daran gezweifelt, dass es erlaubt ist.
ltjax

Antworten:

92

Ich frage mich, was der C ++ - Standard dazu sagt. Ist es legal und garantiert, dass es immer funktioniert?

Ja. Das ist völlig legal. Vollständig standardkonform.

Blah(std::vector<int> vec): vec(vec){}
                             ^   ^                           
                             |   |
                             |    this is the argument to the constructor
                             this is your member data

Da Sie im Standard nach der Referenz gefragt haben, finden Sie hier ein Beispiel.

§12.6.2 / 7

Namen in der Ausdrucksliste eines Mem-Initialisierers werden im Bereich des Konstruktors ausgewertet, für den der Mem-Initialisierer angegeben ist.

[Example:
class X {
 int a;
 int b;
 int i;
 int j;
 public:
 const int& r;
  X(int i): r(a), b(i), i(i), j(this->i) {}
                      //^^^^ note this (added by Nawaz)
};

initialisiert X :: r, um auf X :: a zu verweisen, initialisiert X :: b mit dem Wert des Konstruktorparameters i, initialisiert X :: i mit dem Wert des Konstruktorparameters i und initialisiert X :: j mit dem Wert von X :: i; Dies erfolgt jedes Mal, wenn ein Objekt der Klasse X erstellt wird. ]]

[Hinweis: Da die mem-Initialisierer im Bereich des Konstruktors ausgewertet werden, kann dieser Zeiger in der Ausdrucksliste eines mem-Initialisierers verwendet werden, um auf das zu initialisierende Objekt zu verweisen. ]]

Wie Sie sehen können, gibt es im obigen Beispiel noch weitere interessante Dinge zu beachten und den Kommentar aus dem Standard selbst.


Übrigens, als Randnotiz, warum akzeptieren Sie den Parameter nicht als konstante Referenz:

 Blah(const std::vector<int> & vec): vec(vec) {}
      ^^^^const              ^reference

Es wird eine unnötige Kopie des ursprünglichen Vektorobjekts vermieden.

Nawaz
quelle
Vielen Dank für die schnelle Antwort, aber im Standarddokument (n3290) kann ich keine Initialisierungslisten finden. Welches Kapitel?
Nils
3
In C ++ 0x ist es besser, den Wert als By-Value zu akzeptieren und ihn dann an sein Ziel zu verschieben : Blah(std::vector<int> vec) : vec(std::move(vec)){}. Sie können dies in C ++ 03 folgendermaßen simulieren : Blah(std::vector<int> vecSrc) { std::swap(vec, vecSrc); }.
GManNickG
3
@Tomalak: lol, ich bin immer noch überrascht, dass du das unlesbar findest. Es ist ziemlich einfach und, dachte ich, alltäglich.
GManNickG
1
@ GMan: Ich klassifiziere es immer noch als "Trick". Ich weiß, was es tut, aber aus dem Code geht auf einen Blick nicht hervor, was es bedeutet , denke ich. Sie müssen die Schritte durchdenken, um zu erkennen, dass das ursprüngliche Originalobjekt nirgendwo verschoben wird.
Leichtigkeitsrennen im Orbit
2
Obwohl es legal ist, ist es eine schlechte Praxis. Es trübt das Wasser bei Verwendung von -Wshadow (oder einem gleichwertigen Element) und macht Sie für schädlichere Programmierfehler anfällig.
Rick
15

Es funktioniert garantiert immer (ich benutze es ziemlich oft). Der Compiler weiß, dass die Initialisierungsliste die Form hat: member(value)und weiß daher, dass der erste vecin vec(vec)ein Mitglied sein muss. Für das Argument zum Initialisieren des Elements können nun beide Elemente, Argumente für den Konstruktor und andere Symbole verwendet werden, wie in jedem Ausdruck, der im Konstruktor vorhanden wäre. Zu diesem Zeitpunkt werden die regulären Suchregeln angewendet, und das Argument vecverbirgt das Mitglied vec.

Abschnitt 12.6.2 des Standards befasst sich mit der Initialisierung und erläutert den Prozess mit Absatz 2, der sich mit der Suche nach dem Mitglied befasst, und Absatz 7 mit der Suche nach dem Argument.

Namen in der Ausdrucksliste eines Mem-Initialisierers werden im Bereich des Konstruktors ausgewertet, für den der Mem-Initialisierer angegeben ist. [Beispiel:

class X {
   int a;
   int b;
   int i;
   int j;
public:
   const int& r;
   X(int i): r(a), b(i), i(i), j(this->i) {}
};
David Rodríguez - Dribeas
quelle
2
+1 für den Schlüsselpunkt hier, den alle anderen nicht gesagt haben: Dies funktioniert, weil die gleichen Suchregeln angewendet werden, die im ctor-Body verwendet werden würden, was - und dies ist der Schlüsselpunkt - bedeutet, dass das Argument das Mitglied verbirgt / beschattet. Aus diesem Grund funktioniert dies (für eine Definition des Wortes "Arbeit"; jedes Verstecken / Abschatten ist meiner Meinung nach ein schlechter Stil).
underscore_d
2

Ein zusätzliches Gegenargument oder vielleicht nur etwas, das Sie beachten sollten, ist die Situation, in der die Verschiebungskonstruktion verwendet wird, um die Elementvariable zu initialisieren.

Wenn die Mitgliedsvariable im Hauptteil des Konstruktors verwendet werden muss, muss die Mitgliedsvariable über diesen Zeiger explizit referenziert werden, andernfalls wird die verschobene Variable verwendet, die sich in einem undefinierten Zustand befindet.

template<typename B>
class A {
public:

  A(B&& b): b(std::forward(b)) {
    this->b.test(); // Correct
    b.test(); // Undefined behavior
  }

private:
  B b;
};
Artalizian
quelle
1

Wie andere bereits geantwortet haben: Ja, das ist legal. Und ja, dies wird durch den Standard garantiert, um zu funktionieren.

Und ich finde es jedes Mal schrecklich, wenn ich es sehe, und zwinge mich zu pausieren: " vec(vec)? WTF? Ah ja, vecist eine Mitgliedsvariable ..."

Dies ist einer der Gründe, warum viele, einschließlich ich, gerne eine Namenskonvention verwenden, die deutlich macht, dass eine Mitgliedsvariable eine Mitgliedsvariable ist. Zu den Konventionen, die ich gesehen habe, gehört das Hinzufügen eines Unterstrichsuffixes ( vec_) oder eines m_Präfixes ( m_vec). Dann lautet der Initialisierer: vec_(vec)/ m_vec(vec), was ein Kinderspiel ist.

user1387866
quelle
Es ist einfacher, den Fehler in: x (y), y (y) zu erkennen, als denselben Fehler in: m_x (in_y), m_y (in_y) zu erkennen. Die am wenigsten dekorierten Namen lenken weniger ab und ermöglichen es dem Leser, sich nur auf das Wesentliche zu konzentrieren. Dies führt am Ende zu einer geringeren Wahrscheinlichkeit von Fehlern. Diese Regeln in der Norm wurden nicht zufällig gewählt, sie haben einen Zweck.
Dom