简体   繁体   中英

C++ defining a constant member variable inside class constructor

Usually when you have a constant private member variable in your class, which only has a getter but no setter, it would look something like this:

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

Now what I'm trying to do, is have a constant int member variable like that, but instead of defining it in the initializing section like so: : m_value(value) I need to take an other object - I'll use a vector in this example - as the constructor's parameter, and set m_value based on the parameter object. In this case, I'll try to do vector's size + 1, if the size is above 0. So this is what I did:

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

But I get an error uninitialized member 'Example::m_value' with 'const' type 'const int' and if I init m_value inside the initializing section, I get the error assignment of read-only data-member 'Example::m_value' which all makes sense to me, I'm supposed to get those errors, but how could I go around them?

Edit: Only way I could edit m_value is inside the object itself (since m_value is private). Having only getter would limit me from setting m_value to anything other than what it's set in the constructor. Do I benefit anything from having constant int as a member variable?

Use a static member function the compute to result you need and call that function in the initialization list. Like this:

// 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 this particular case, the function is so very simple you can simply use the ternary operator (aka : m_value(myVec.size() > 0 ? int(myVec.size() + 1) : int(-1) ) in the constructor to directly compute the value at initialization. This looked like an example, so I gave you a very general method of solving the problem, even when the method of computing the answer you need might be very complex.

The general issue is that constant member variables (and member variables that are references too BTW) must be initialized in the initializer list. But initializers can be expressions, which means they can call functions. Since this initialization code is pretty specific to the class, it should be a function private (or maybe protected) to the class. But, since it's called to create a value before the class is constructed it can't depend on a class instance to exist, hence no this pointer. That means it needs to be a static member function.

Now, the type of myVec.size() is std::vector<Example*>::size_t , and that type is unsigned. And you're using a sentinel value of -1, which isn't. And you're storing it in an int which may not be the right size to hold it anyway. If your vector is small, this likely isn't an issue. But if your vector acquires a size based on external input, or if you don't know how large it will get, or any number of other factors, this will become an issue. You should be thinking about that and adjusting your code accordingly.

First, the variable is defined in the class definition, not in the constructor. It's initialized in the constructor.

Second, the way to do that is just like what your constructor currently does: store the value in it from the initializer list:

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

You have two basic options. One is to use the conditional operator, which is fine for simple conditions like yours:

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

For more complex things, you can delegate the computation to a member function. Be careful not to call virtual member functions inside it, as it will be called during construction. It's safest to make it 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;
    }
  }
};

The latter works with out-of-class definitions as well, of course. I've placed them in-class to conserve space & typing.

This answer addresses a problem with all of the other answers:

This suggestion is bad:

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

The conditional operator brings its second and third operand to a common type, regardless of the ultimate selection.

In this case the common type of size_t and int is size_t . So if the vector is empty, the value (size_t)-1 is assigned to the int m_value, which is an out-of-range conversion, invoking implementation-defined behaviour.


To avoid relying on implementation-defined behaviour the code could be:

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

Now, this retains another problem that the original code had: out of range conversion when myVec.size() >= INT_MAX . In robust code this problem should also be addressed.

I would personally prefer the suggestion to add a helper function, which performs this range test and throws an exception if the value is out of range. The one-liner is possible although the code is starting to get hard to read:

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

Of course there are a few other ways to deal with this problem more cleanly, eg use size_t for m_value and either have (size_t)-1 as the sentinel value, or preferably avoid the need for a sentinel value entirely.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM