RAII: Initializing data member in const method
-
21-09-2019 - |
Question
In RAII, resources are not initialized until they are accessed. However, many access methods are declared constant. I need to call a mutable
(non-const) function to initialize a data member.
Example: Loading from a data base
struct MyClass
{
int get_value(void) const;
private:
void load_from_database(void); // Loads the data member from database.
int m_value;
};
int
MyClass ::
get_value(void) const
{
static bool value_initialized(false);
if (!value_initialized)
{
// The compiler complains about this call because
// the method is non-const and called from a const
// method.
load_from_database();
}
return m_value;
}
My primitive solution is to declare the data member as mutable
. I would rather not do this, because it suggests that other methods can change the member.
How would I cast the load_from_database()
statement to get rid of the compiler errors?
Solution
This is not RAII. In RAII you would initialize it in the constructor, which would solve your problems.
So, what you are using here is Lazy
. Be it lazy initialization or lazy computation.
If you don't use mutable
, you are in for a world of hurt.
Of course you could use a const_cast
, but what if someone does:
static const MyClass Examplar;
And the compiler decides it is a good candidate for Read-Only memory ? Well, in this case the effects of the const_cast
are undefined. At best, nothing happens.
If you still wish to pursue the const_cast
route, do it as R Samuel Klatchko
do.
If you thought over and think there is likely a better alternative, you can decide to wrap your variable. If it was in class of its own, with only 3 methods: get
, set
and load_from_database
, then you would not worry about it being mutable
.
OTHER TIPS
You are basically implementing a caching mechanism. Personally I think it's OK to mark cached data as mutable.
As Matthieu already pointed out, what you're trying to do here has little (if anything) to do with RAII. Likewise, I doubt that any combination of const
and mutable
is really going to help. const
and mutable
modify the type, and apply equally to all access to an object of that type.
What you seem to want is for a small amount of code to have write access, and anything else only read access to the value. Given the basic design of C++ (and most similar languages), the right way to do that is to move the variable into a class of its own, with the small amount of code that needs write access as part of (or possibly a friend of) that class. The rest of the world is given its read-only access via the class' interface (i.e., a member function that retrieves the value).
The (presumably stripped down) MyClass
you've posted is pretty close to right -- you just need to use that by itself, instead of as part of a larger class with lots of other members. The main things to change would be 1) the name from MyClass
to something like lazy_int
, and 2) (at least by my preference) get_value()
should probably be renamed to operator int()
. Yes, m_value
will probably need to be mutable, but this doesn't allow other code to write the value, simply because other code doesn't have access to the value itself at all.
Then you embed an object of that type into your larger class. The code in that outer class can treat it as an int (on a read-only basis) thanks to its operator int()
, but can't write it, simply because the class doesn't give any way to do so.
[ LOOK MA! NO CASTS! :)) ]
struct DBValue
{
int get_value();
private:
void load_from_database();
int value;
};
struct MyClass
{
MyClass(): db_value(new DBValue()) {}
~MyClass() { delete db_value; }
int get_value() const;
private:
DBValue * const db_value;
};
int MyClass::get_value() const
{
return db_value->get_value(); // calls void load_from_database() if needed
}
The idea is to have a politically correct MyClass
with const
methods not mutating anything but calling both const
and non-const
methods of aggregated objects via const pointers.
Don't use const_cast here, or you're asking for trouble. Using mutable in this case shouldn't be a problem, but if the profiler didn't suggest otherwise then I think users would be less surprised to see an object that is expensive to construct than an accessor method that is expensive to call the first time.
If your method changes the state of the object (e.g. by changing the state of the underlying database), then the method should not be const. In that case you should have a separate, non-const load
-method, that has to be called before the const
getter can be called.
This method would require neither const_cast
not mutable
, and would make the potentially expensive operation explicit.