web-dev-qa-db-fra.com

C++ définissant une variable membre constante dans le constructeur de la classe

Habituellement, lorsque vous avez une variable membre privée constante dans votre classe, qui ne comporte qu'un getter mais pas un séparateur, cela ressemble à ceci:

// 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;
}

Maintenant, ce que j'essaie de faire, c'est d'avoir une variable membre int constante comme celle-ci, mais au lieu de la définir dans la section d'initialisation, comme ceci: : m_value(value) Je dois prendre un autre objet - je vais utiliser un vecteur dans cet exemple - en tant que paramètre du constructeur et définissez m_value en fonction de l'objet paramètre. Dans ce cas, je vais essayer de faire la taille du vecteur + 1, si la taille est supérieure à 0. Voici ce que j'ai fait:

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

Mais je reçois une erreur uninitialized member 'Example::m_value' with 'const' type 'const int' et si j’initialise m_value dans la section d’initialisation, j’obtiens l’erreur assignment of read-only data-member 'Example::m_value' qui me semble tout à fait sensée. Je suis censée avoir ces erreurs, mais comment puis-je les contourner?

Éditer: La seule façon dont je pourrais éditer m_value est à l'intérieur de l'objet lui-même (m_value étant privé). N'avoir que le getter m'empêcherait de définir m_value sur autre chose que ce qui est défini dans le constructeur. Est-ce que je profite de la constante int en tant que variable membre? 

15
user1632861

Utilisez une fonction de membre statique permettant de calculer le résultat souhaité et appelez-la dans la liste d'initialisation. Comme ça:

// 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;
    }
}

Dans ce cas particulier, la fonction est tellement simple que vous pouvez simplement utiliser l'opérateur ternaire (aussi appelé : m_value(myVec.size() > 0 ? int(myVec.size() + 1) : int(-1)) dans le constructeur pour calculer directement la valeur à l'initialisation. Cela ressemblait à un exemple et je vous ai donc présenté une méthode très générale de résolution du problème, même lorsque la méthode de calcul de la réponse dont vous avez besoin peut être très complexe.

Le problème général est que les variables de membre constantes (et les variables de membre qui sont aussi des références BTW) doivent être initialisées dans la liste des initialiseurs. Mais les initialiseurs peuvent être des expressions, ce qui signifie qu'ils peuvent appeler des fonctions. Comme ce code d'initialisation est assez spécifique à la classe, il devrait s'agir d'une fonction privée (ou peut-être protégée) pour la classe. Mais comme il est appelé pour créer une valeur avant la construction de la classe, il ne peut pas dépendre d'une instance de classe pour exister, d'où l'absence de pointeur this. Cela signifie qu'il doit s'agir d'une fonction membre statique.

Maintenant, le type de myVec.size() est std::vector<Example*>::size_t et ce type est non signé. Et vous utilisez une valeur sentinelle de -1, ce qui n’est pas le cas. Et vous le stockez dans une int qui n’a peut-être pas la bonne taille pour le conserver de toute façon. Si votre vecteur est petit, ce n'est probablement pas un problème. Mais si votre vecteur acquiert une taille basée sur une entrée externe, ou si vous ne savez pas quelle taille il obtiendra, ou un nombre quelconque d'autres facteurs, cela deviendra un problème. Vous devriez y penser et ajuster votre code en conséquence.

29
Omnifarious

Premièrement, la variable est define dans la définition de la classe, pas dans le constructeur. C'est initialisé dans le constructeur.

Deuxièmement, la manière de procéder est comme ce que votre constructeur fait actuellement: stockez la valeur dans la liste d'initialisation:

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

Cette réponse résout un problème avec toutes les autres réponses:

Cette suggestion est mauvaise:

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

L'opérateur conditionnel amène ses deuxième et troisième opérandes à un type commun, quelle que soit la sélection finale. 

Dans ce cas, le type commun de size_t et int est size_t. Ainsi, si le vecteur est vide, la valeur (size_t)-1 est affectée à la int m_value, qui est une conversion hors limites, invoquant un comportement défini par l'implémentation.


Pour éviter de s'appuyer sur un comportement défini par l'implémentation, le code pourrait être:

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

Maintenant, ceci conserve un autre problème que le code d'origine avait: une conversion hors de portée lorsque myVec.size() >= INT_MAX. En code robuste, ce problème doit également être traité. 

Personnellement, je préférerais la suggestion d’ajouter une fonction d’aide, qui effectue ce test de plage et lève une exception si la valeur est en dehors de la plage. Le one-liner est possible bien que le code commence à être difficile à lire:

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

Bien sûr, il existe quelques autres moyens de traiter ce problème plus proprement, par exemple. utilisez size_t pour m_value et avez soit (size_t)-1 comme valeur sentinelle, soit évitez de préférence le besoin d'une valeur sentinelle entièrement.

0
M.M

Vous avez deux options de base. La première consiste à utiliser l'opérateur conditionnel, ce qui convient pour des conditions simples comme la vôtre:

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

Pour des tâches plus complexes, vous pouvez déléguer le calcul à une fonction membre. Veillez à ne pas appeler les fonctions membres virtuelles à l'intérieur de celui-ci, car il sera appelé lors de la construction. Il est plus sûr de le faire static:

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;
    }
  }
};

Ce dernier fonctionne bien entendu avec des définitions hors classe. Je les ai placés en classe pour économiser de l'espace et la dactylographie.

0
Angew