views:

273

answers:

6

What is the proper way to implement a getter method for a lazily-initialized member variable and maintain const-correctness? That is, I would like to have my getter method be const, because after the first time it is used, it's a normal getter method. It is only the first time (when the object is first initialized) that const does not apply. What I would like to do:

class MyClass {
  MyClass() : expensive_object_(NULL) {}
  QObject* GetExpensiveObject() const {
    if (!expensive_object_) {
      expensive_object = CreateExpensiveObject();
    }
    return expensive_object_;
  }
private:
  QObject *expensive_object_;
};

Can I eat my cake and have it too?

+12  A: 

That's fine and is the typical way of doing it.

You will have to declare expensive_object_ as mutable

mutable QObject *expensive_object_; 

mutable basically means "I know I'm in a const object, but modifying this won't break const-ness."

James Curran
@Jon-Eric: If it isn't really const, it should be mutable. Having a const_cast in one spot doesn't express that well.
David Thornley
@David: My take is that it really IS const and that one spot is the ONLY place it will be modified (initialized). If many methods/places need to modify it (like a cache), then mutable would make more sense.
Jon-Eric
We are talking about the implementation details of a presumably protected/private variable. What does it matter if other methods can also change it? Other methods could also pull out a const cast and change it too. There is no additional encapsulation by not using mutable. However, making it mutable tells the reader of the class definition something valuable to know.
frankc
@user275455: Const-safety is helpful for the class maintainer as well as clients of the class. It matters because you don't want to accidentally mutate a member in a const method. Without mutable, the compiler will catch that for you. You say clients shouldn't worry about protected/private details (true) but then you about-face and say the declaration of the mutable private variable is valuable to clients (not true).
Jon-Eric
You could just as easily have another maintainer see const in the declaration, thinks it never changes at all, rely on that and introduce a bug that way. I don't really have a problem with the const cast solution either, but I do think that downvoting the mutable solution is wrong-headed;each approach has merit. In my mind, the mutable solution has more merit, but just marginally so.
frankc
@user275455: That's a very clear explanation. Thank you. I'll take back my downvote and save them in the future for answers that are strictly wrong and let others' upvotes sort out 'best'.
Jon-Eric
I was hoping to avoid `mutable` for the reasons others have outlined (it does more than I want and can't be "un-mutabled.") But it is a reasonable way to go, especially when wrapped per the accepted answer.
Dave
+5  A: 

Make expensive_object_ mutable.

bshields
+4  A: 

Use a const_cast to side-step const in that one specific place.

QObject* GetExpensiveObject() const {
  if (!expensive_object_) {
    const_cast<QObject *>(expensive_object_) = CreateExpensiveObject();
  }
  return expensive_object_;
}

IMHO, this is better than making expensive_object_ mutable because you don't lose the const-safety in all your other methods.

Jon-Eric
Why the downvote?
Jon-Eric
Agreed. While I _do_ grep the codebase for const_cast and C-style casts periodically to make sure there are no const violations, this is a case where I think it would be justifiable.(Although I'm not sure I agree with your -1 on the mutable answer... They're both good ways of handling the problem, just with different consequences: alarming code-smelly syntax vs. more mutability than needed...)
leander
I'm not the downvoter, but I believe this could lead to undefined behavior if the MyClass instance is created as `const`.
Fred Larson
Casting away contness and modifying the object is undefined behavior unless the object is not actually const. Which you can't tell from its usage here.
Martin York
@Martin: Does my edit make it any safer? Also, do you have a link which explains the situation you are worried about?
Jon-Eric
@Jon-Eric. That doesn't fix it. I suppose the problem is that when you declare an object as const (without mutable members) the compiler might decide to put the object on a read-only memory page somewhere. See http://www.parashift.com/c++-faq-lite/const-correctness.html
Ken Bloom
@Martin: that'll teach me to go read the standard before mouthing off =) 5.2.11.7: "[Note: Depending on the type of the object, a write operation through the pointer, lvalue or pointer to datamember resulting from a const_cast that casts away a const-qualifier68) may produce undefined behavior(7.1.5.1). ]" 7.1.5.1.4: "Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a constobject during its lifetime (3.8) results in undefined behavior." This is probably to allow for things like placement in write-protected memory, at a guess?
leander
@KenBloom: Thank you for the link! I learned something new.
Jon-Eric
This is exactly what I would like to do. But it sounds like it risks undefined behavior. In my case, the objects are not **that** expensive that I want to risk it (and increase the restrictions on how the API is used), but in another case, this might just work.
Dave
+6  A: 

I propose encapsulating James Curran's answer into a class of its own if you do this frequently:

template <typename T>
class suspension{
   std::tr1::function<T()> initializer;
   mutable T value;
   mutable bool initialized;
public:
   suspension(std::tr1::function<T()> init):
      initializer(init),initialized(false){}
   operator T const &() const{
      return get();
   }
   T const & get() const{
      if (!initialized){
         value=initializer();
         initialized=true;
      }
      return value;
   }
};

Now use this in your code as follows:

class MyClass {
  MyClass() : expensive_object_(CreateExpensiveObject) {}
  QObject* GetExpensiveObject() const {
    return suspended.get();
  }
private:
  suspension<QObject *> expensive_object_;
};
Ken Bloom
Hah, beat me to it... +1 =)
leander
I like your answer too. Implementing a pointer interface may be more consistent than a reference interface.
Ken Bloom
This appears to be the most universal solution that "plays nice" with the c++ spec. It's ugly under the surface, but the public API stays clean.
Dave
I'm surprised I couldn't find a Boost library for this.
Ken Bloom
Out of curiosity, how did you come up with the name `suspension` for this class?
bshields
Why not `const suspension<QObject *> expensive_object_;` ?
Dan Breslau
@bshields: That's the term used in *Purely Functional Data Structures* by Chris Okasaki. I could have called it `lazy` (following Haskell and Scala precedent), but the Boost libraries use that term differently for their quasi-lambda stuff, so I decided it wasn't such a good idea to engender that confusion.
Ken Bloom
@Dan Breslau: I figure the constness of a member variable should just usually follow from whatever the object is. But it would work here if you used `const suspension<QObject *>` as the member variable as well. I'm not really sure what C++ convention is in this regard.
Ken Bloom
@Ken Bloom: The way I look at it, the constness of the suspension object's methods is an implementation detail as far as `MyClass` is concerned. (OK, it's a bit more than that, seeing as `MyClass` arranged for the member to be of that type -- but I digress.) If MyClass cares about the constness of the member, adding the `const` keyword to the declaration of `expensive_object` seems to me to be the right way to go.
Dan Breslau
+2  A: 

Have you considered a wrapper class? You might be able to get away with something like a smart pointer, with only const-returning versions of operator* and operator-> and maybe operator[]... You can get scoped_ptr-like behavior out of it as a bonus.

Let's give this a shot, I'm sure people can point out a few flaws:

template <typename T>
class deferred_create_ptr : boost::noncopyable {
private:
    mutable T * m_pThing;
    inline void createThingIfNeeded() const { if ( !m_pThing ) m_pThing = new T; }
public:
    inline deferred_create_ptr() : m_pThing( NULL ) {}
    inline ~deferred_create_ptr() { delete m_pThing; }

    inline T * get() const { createThingIfNeeded(); return m_pThing; }

    inline T & operator*() const { return *get(); }
    inline T * operator->() const { return get(); }

    // is this a good idea?  unintended conversions?
    inline T * operator T *() const { return get(); }
};

Use of type_traits might make this better...

You'd need different versions for array pointers, and you might have to play around a bit with a creator functor or factory object or something if you wanted to pass in arguments to T's constructor.

But you could use it like this:

class MyClass {
public:
    // don't need a constructor anymore, it comes up NULL automatically
    QObject * getExpensiveObject() const { return expensive_object_; }

protected:
    deferred_create_ptr<QObject> expensive_object_;
};

Time to go off and compile this and see if I can break it... =)

leander
He'll probably want to look at both of our versions, just in case the default constructor isn't what he had in mind, or in case null was a valid return value from CreateExpensiveObject.
Ken Bloom
A: 

Your getter isn't really const since it does change the content of the object. I think you're over thinking it.

Jay
This is precisely what `mutable` was invented for -- so that you could declare something `const` even though you have to modify its memory, even though you know that its interface will always look the same.
Ken Bloom