views:

195

answers:

6

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?

+16  A: 

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.

Matthieu M.
Have an upvote for pointing out that this isn't RAII
Glen
...And for being against const_cast which is almost always wrong
laginimaineb
+5  A: 

You are basically implementing a caching mechanism. Personally I think it's OK to mark cached data as mutable.

StackedCrooked
More, it seems to be just the rationale for the very existence of _mutable_ (members).
mlvljr
+1  A: 

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.

wilhelmtell
A: 

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.

Space_C0wb0y
The theme of the issue is that the variable is initialized using `lazy initialization`, which is the first time it is accessed. All future accesses to this variable are read-only. The database is not changed, only the order of initialization.
Thomas Matthews
+2  A: 

[ 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.

mlvljr
This is essentially what Jerry Coffin was stating, but you provided example code.
Thomas Matthews
@Thomas Matthews Actually I was (partially) inspired by the last part of Matthieu M.'s post.
mlvljr
+5  A: 

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.

Jerry Coffin