36

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?

Dave Mateer
  • 16,802
  • 15
  • 90
  • 142

9 Answers9

25

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

leander
  • 8,287
  • 1
  • 27
  • 42
James Curran
  • 95,648
  • 35
  • 171
  • 253
  • 1
    @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 Jul 30 '10 at 19:44
  • 1
    @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 Jul 30 '10 at 19:47
  • 5
    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 Jul 30 '10 at 19:49
  • 4
    @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 Jul 30 '10 at 20:05
  • 2
    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 Jul 30 '10 at 20:18
  • 2
    @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 Jul 30 '10 at 20:25
  • 1
    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 Mateer Aug 03 '10 at 13:41
24

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 expensive_object_.get();
  }
private:
  suspension<QObject *> expensive_object_;
};
Community
  • 1
  • 1
Ken Bloom
  • 52,354
  • 11
  • 101
  • 164
  • I like your answer too. Implementing a pointer interface may be more consistent than a reference interface. – Ken Bloom Jul 30 '10 at 20:15
  • 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 Mateer Aug 03 '10 at 13:40
  • 1
    I'm surprised I couldn't find a Boost library for this. – Ken Bloom Aug 03 '10 at 13:47
  • Out of curiosity, how did you come up with the name `suspension` for this class? – bshields Aug 03 '10 at 14:01
  • Why not `const suspension expensive_object_;` ? – Dan Breslau Aug 03 '10 at 14:04
  • @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 Aug 03 '10 at 14:16
  • @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` as the member variable as well. I'm not really sure what C++ convention is in this regard. – Ken Bloom Aug 03 '10 at 14:18
  • 1
    @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 Aug 04 '10 at 05:13
  • Very interesting solution. I have two sugestions. First use boost::optional for T value, second I think your example code is wrong. It should be expensive_object_.get() and not suspended.get() – Martin Jul 30 '12 at 20:00
7

Make expensive_object_ mutable.

bshields
  • 3,503
  • 14
  • 15
5

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
  • 15,895
  • 8
  • 58
  • 93
  • 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 Jul 30 '10 at 19:47
  • 2
    I'm not the downvoter, but I believe this could lead to undefined behavior if the MyClass instance is created as `const`. – Fred Larson Jul 30 '10 at 19:47
  • 7
    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 Jul 30 '10 at 19:49
  • @Martin: Does my edit make it any safer? Also, do you have a link which explains the situation you are worried about? – Jon-Eric Jul 30 '10 at 19:54
  • 2
    @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 Jul 30 '10 at 20:12
  • 3
    @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 data member 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 const object 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 Jul 30 '10 at 20:14
  • 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 Mateer Aug 03 '10 at 13:39
3

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
  • 8,287
  • 1
  • 27
  • 42
  • 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 Jul 30 '10 at 20:17
0

Proposing an even fancier solution here, but it doesn't handle types with no default constructor...

Community
  • 1
  • 1
James Hugard
  • 3,172
  • 1
  • 23
  • 32
0

I've crated a class template Lazy<T> with the following features:

  • Familiar interface similar to standard smart pointers
  • Supports types without default constructor
  • Supports (movable) types without copy constructor
  • Thread-safe
  • Copyable using reference semantics: All copies share the same state; their value is created only once.

Here's how you use it:

// Constructor takes function
Lazy<Expensive> lazy([] { return Expensive(42); });

// Multiple ways to access value
Expensive& a = *lazy;
Expensive& b = lazy.value();
auto c = lazy->member;

// Check if initialized
if (lazy) { /* ... */ }

Here's the implementation.

#pragma once
#include <memory>
#include <mutex>

// Class template for lazy initialization.
// Copies use reference semantics.
template<typename T>
class Lazy {
    // Shared state between copies
    struct State {
        std::function<T()> createValue;
        std::once_flag initialized;
        std::unique_ptr<T> value;
    };

public:
    using value_type = T;

    Lazy() = default;

    explicit Lazy(std::function<T()> createValue) {
        state->createValue = createValue;
    }

    explicit operator bool() const {
        return static_cast<bool>(state->value);
    }

    T& value() {
        init();
        return *state->value;
    }

    const T& value() const {
        init();
        return *state->value;
    }

    T* operator->() {
        return &value();
    }

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

    T& operator*() {
        return value();
    }

    const T& operator*() const {
        return value();
    }

private:
    void init() const {
        std::call_once(state->initialized, [&] { state->value = std::make_unique<T>(state->createValue()); });
    }

    std::shared_ptr<State> state = std::make_shared<State>();
};
Daniel Wolf
  • 10,328
  • 9
  • 42
  • 73
  • With regard to the MIT license: See https://meta.stackexchange.com/questions/12527/do-i-have-to-worry-about-copyright-issues-for-code-posted-on-stack-overflow – Matthias Jun 30 '17 at 16:51
  • @Matthias: Thanks for letting me know! When I started out on StackOverflow, I'm pretty sure that they had a more restrictive default license. Back then, putting my code under the MIT License made sense. I'm glad it's CC now, so I've removed that sentence. – Daniel Wolf Jul 01 '17 at 18:41
0

I played a bit with this topic and came up with an alternative solution in case you use C++11. Consider the following:

class MyClass 
{
public:
    MyClass() : 
        expensiveObjectLazyAccess() 
    {
        // Set initial behavior to initialize the expensive object when called.
        expensiveObjectLazyAccess = [this]()
        {
            // Consider wrapping result in a shared_ptr if this is the owner of the expensive object.
            auto result = std::shared_ptr<ExpensiveType>(CreateExpensiveObject());

            // Maintain a local copy of the captured variable. 
            auto self = this;

            // overwrite itself to a function which just returns the already initialized expensive object
            // Note that all the captures of the lambda will be invalidated after this point, accessing them 
            // would result in undefined behavior. If the captured variables are needed after this they can be 
            // copied to local variable beforehand (i.e. self).
            expensiveObjectLazyAccess = [result]() { return result.get(); };

            // Initialization is done, call self again. I'm calling self->GetExpensiveObject() just to
            // illustrate that it's safe to call method on local copy of this. Using this->GetExpensiveObject()
            // would be undefined behavior since the reassignment above destroys the lambda captured 
            // variables. Alternatively I could just use:
            // return result.get();
            return self->GetExpensiveObject();
        };
    }

    ExpensiveType* GetExpensiveObject() const 
    {
        // Forward call to member function
        return expensiveObjectLazyAccess();
    }
private:
    // hold a function returning the value instead of the value itself
    std::function<ExpensiveType*()> expensiveObjectLazyAccess;
};

Main idea is to hold a function returning the expensive object as member instead of the object itself. In the constructor initialize with a function that does the following:

  • Initializes the expensive object
  • Replaces itself with a function which captures the already initialized object and just returns it.
  • Returns the object.

What I like about this is that the initialization code is still written in the constructor (where I'd naturally put it if lazyness was not needed) even though it will only get executed when the first query for the expensive object happens.

A downside of this approach is that std::function reassigns itself within it's execution. Accessing any non static members (captures in case of using lambda's) after the reassignment would result in undefined behavior so this requires extra attention. Also this is kind of a hack since GetExpensiveObject() is const but it still modifies a member attribute on first call.

In production code I'd probably prefer to just make the member mutable as James Curran described. This way the public API of your class clearly states that the member is not considered part of the objects state, therefore it doesn't affect constness.

After a bit more thinking I figured that std::async with std::launch::deferred could also be used in combination with a std::shared_future in order to be able to retrieve the result multiple times. Here is the code:

class MyClass
{
public:
    MyClass() :
        deferredObj()
    {
        deferredObj = std::async(std::launch::deferred, []()
        {
            return std::shared_ptr<ExpensiveType>(CreateExpensiveObject());
        });
    }

    const ExpensiveType* GetExpensiveObject() const
    {
        return deferredObj.get().get();
    }
private:
    std::shared_future<std::shared_ptr<ExpensiveType>> deferredObj;
};
otama
  • 46
  • 3
-2

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

Jay
  • 12,504
  • 3
  • 36
  • 65
  • 1
    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 Jul 30 '10 at 20:14