web-dev-qa-db-de.com

C++, das eine konstante Membervariable innerhalb des Klassenkonstruktors definiert

Wenn Sie in Ihrer Klasse eine konstante private Member-Variable haben, die nur einen Getter, aber keinen Setter enthält, würde dies normalerweise so aussehen:

// Example.h
class Example {
    public:
        Example(const int value);
        const int getValue() const;
    private:
        const int m_value;
};


// Example.cpp
#include "Example.h"

Example::Example(const int value)
: m_value(value)
{
}

const int Example::getValue() const
{
    return m_value;
}

Nun versuche ich, eine konstante int-Membervariable wie diese zu haben, aber anstatt sie im Initialisierungsabschnitt wie folgt zu definieren: : m_value(value) Ich muss ein anderes Objekt nehmen - ich verwende in diesem Beispiel einen Vektor - als Konstruktorparameter und setzen Sie m_value basierend auf dem Parameterobjekt. In diesem Fall versuche ich, die Größe des Vektors + 1 auszuführen, wenn die Größe über 0 liegt. Daher habe ich Folgendes getan:

Example::Example(std::vector<Example*> myVec)
{
    if (myVec.size()) {
        m_value = myVec.size() + 1;
    }
    else {
        m_value = -1;
    }
}

Ich erhalte jedoch einen Fehler uninitialized member 'Example::m_value' with 'const' type 'const int' und wenn ich m_value im Initialisierungsabschnitt initiere, erhalte ich den Fehler assignment of read-only data-member 'Example::m_value', der für mich sinnvoll ist. Ich sollte diese Fehler erhalten, aber wie kann ich sie umgehen?

Edit: Die einzige Möglichkeit, m_value zu bearbeiten, ist im Objekt selbst (da m_value privat ist). Nur Getter zu haben, würde mich davon abhalten, m_value auf einen anderen Wert als den im Konstruktor festgelegten Wert zu setzen. Profitiere ich davon, konstantes int als Membervariable zu haben? 

15
user1632861

Verwenden Sie eine statische Memberfunktion, um das Ergebnis zu berechnen, das Sie benötigen, und rufen Sie diese Funktion in der Initialisierungsliste auf. So was:

// Example.h
class Example {
    public:
        Example(const int value);
        Example(std::vector<Example*> myVec);

        const int getValue() const;
    private:
        const int m_value;

        static int compute_m_value(::std::vector<Example*> &myVec);
};

// Example.cpp
#include "Example.h"

Example::Example(const int value)
: m_value(value)
{
}

Example::Example(std::vector<Example*> myVec)
: m_value(compute_m_value(myVec))
{
}

const int Example::getValue() const
{
    return m_value;
}

int Example::compute_m_value(::std::vector<Example*> &myVec)
{
    if (myVec.size()) {
        return myVec.size() + 1;
    }
    else {
        return -1;
    }
}

In diesem speziellen Fall ist die Funktion so einfach, dass Sie einfach den ternären Operator (aka : m_value(myVec.size() > 0 ? int(myVec.size() + 1) : int(-1)) im Konstruktor verwenden können, um den Wert bei der Initialisierung direkt zu berechnen. Dies sah wie ein Beispiel aus, daher habe ich Ihnen eine sehr allgemeine Methode zur Lösung des Problems gegeben, selbst wenn die Methode der Berechnung der Antwort, die Sie benötigen, sehr komplex sein kann.

Das generelle Problem ist, dass konstante Member-Variablen (und Member-Variablen, die zu BTW verweisen) müssen in der Initialisierungsliste initialisiert werden. Initialisierer können jedoch Ausdrücke sein, dh sie können Funktionen aufrufen. Da dieser Initialisierungscode für die Klasse ziemlich spezifisch ist, sollte er eine Funktion sein, die für die Klasse privat (oder möglicherweise geschützt) ist. Da sie jedoch aufgerufen wird, um einen Wert zu erstellen, bevor die Klasse erstellt wird, kann sie nicht von einer Klasseninstanz abhängen, daher kein this-Zeiger. Das heißt, es muss eine statische Memberfunktion sein.

Der Typ von myVec.size() ist jetzt std::vector<Example*>::size_t und dieser Typ ist unsigniert. Und Sie verwenden einen Sentinel-Wert von -1, was nicht der Fall ist. Und Sie speichern es in einer int, die möglicherweise nicht die richtige Größe hat, um es trotzdem zu halten. Wenn Ihr Vektor klein ist, ist dies wahrscheinlich kein Problem. Wenn Ihr Vektor jedoch eine Größe erhält, die auf externen Eingaben basiert, oder wenn Sie nicht wissen, wie groß der Vektor sein wird, oder eine Reihe anderer Faktoren, wird dies zu einem Problem. Sie sollten darüber nachdenken und Ihren Code entsprechend anpassen.

29
Omnifarious

Zunächst wird die Variable definiert in der Klassendefinition, nicht im Konstruktor. Es ist initialisiert im Konstruktor.

Zweitens ist der Weg genau so wie der Konstruktor derzeit: Speichern Sie den Wert aus der Initialisierungsliste:

Example::Example(std::vector<Example*> myVec)
    : m_value(myVec.size() ? myVec.size() + 1 : -1) {
}
5
Pete Becker

Diese Antwort spricht ein Problem mit allen anderen Antworten an:

Dieser Vorschlag ist schlecht:

m_value(myVec.size() ? myVec.size() + 1 : -1)

Der Bedingungsoperator bringt seinen zweiten und dritten Operanden unabhängig von der endgültigen Auswahl auf einen gemeinsamen Typ. 

In diesem Fall ist der übliche Typ von size_t und intsize_t. Wenn der Vektor also leer ist, wird der Wert (size_t)-1 dem Wert int m_value zugewiesen. Hierbei handelt es sich um eine Konvertierung außerhalb des gültigen Bereichs, die das durch die Implementierung definierte Verhalten aufruft.


Um zu vermeiden, sich auf implementierungsdefiniertes Verhalten zu verlassen, könnte der Code Folgendes sein

m_value(myVec.size() ? (int)myVec.size() + 1 : -1)

Jetzt bleibt ein anderes Problem bestehen, das der ursprüngliche Code hatte: Konvertierung außerhalb des Bereichs, wenn myVec.size() >= INT_MAX. In robustem Code sollte dieses Problem ebenfalls angegangen werden. 

Ich würde den Vorschlag persönlich bevorzugen, eine Hilfsfunktion hinzuzufügen, die diesen Bereichstest durchführt und eine Ausnahme auslöst, wenn der Wert außerhalb des Bereichs liegt. Der Einzeiler ist möglich, obwohl der Code schwer zu lesen ist:

m_value( (myVec.empty() || myVec.size() >= INT_MAX) ? -1 : (int)myVec.size() + 1 )

Natürlich gibt es einige andere Möglichkeiten, dieses Problem sauberer zu behandeln, z. Verwenden Sie size_t für m_value und verwenden Sie entweder (size_t)-1 als Sentinel-Wert, oder vermeiden Sie vorzugsweise die Verwendung eines Sentinel-Werts.

0
M.M

Sie haben zwei grundlegende Optionen. Verwenden Sie den Bedingungsoperator, der für einfache Bedingungen wie Ihre geeignet ist:

Example::Example(const std::vector<Example*> &myVec)
  : m_value( myVec.size() ? myVec.size() + 1 : -1)
{}

Bei komplexeren Dingen können Sie die Berechnung an eine Member-Funktion delegieren. Achten Sie darauf, dass Sie keine virtuellen Elementfunktionen in diesem Ordner aufrufen, da diese während der Konstruktion aufgerufen werden. Es ist am sichersten, es static zu machen:

class Example
{
  Example(const std::vector<Example*> &myVec)
    : m_value(initialValue(myVec))
  {}

  static int initialValue(const std::vector<Example*> &myVec)
  {
    if (myVec.size()) {
      return myVec.size() + 1;
    } else {
      return -1;
    }
  }
};

Letzteres arbeitet natürlich auch mit Definitionen außerhalb der Klasse. Ich habe sie in der Klasse platziert, um Platz und Schreibarbeit zu sparen.

0
Angew