Is there C++ lazy pointer?
Asked Answered
D

4

14

I need a shared_ptr like object, but which automatically creates a real object when I try to access its members.

For example, I have:

class Box
{
public:
    unsigned int width;
    unsigned int height;
    Box(): width(50), height(100){}
};

std::vector< lazy<Box> > boxes;
boxes.resize(100);

// at this point boxes contain no any real Box object.
// But when I try to access box number 50, for example,
// it will be created.

std::cout << boxes[49].width;

// now vector contains one real box and 99 lazy boxes.

Is there some implementation, or I should to write my own?

Degauss answered 18/5, 2009 at 15:0 Comment(0)
Q
19

It's very little effort to roll your own.

template<typename T>
class lazy {
public:
    lazy() : child(0) {}
    ~lazy() { delete child; }
    T &operator*() {
        if (!child) child = new T;
        return *child;
    }
    // might dereference NULL pointer if unset...
    // but if this is const, what else can be done?
    const T &operator*() const { return *child; }
    T *operator->() { return &**this; }
    const T *operator->() const { return &**this; }
private:
    T *child;
};

// ...

cout << boxes[49]->width;
Quarto answered 18/5, 2009 at 15:24 Comment(12)
it will make sense to contain child as auto_ptrMonica
But how would you initialize boxes[49]->width to have a non-trivial initialized value (i.e., not 0)? You'd probably want to have an interface that lets the constructor for *(boxes[49]) receive its index as an argument, so that it can distinguish itself from the other boxes. That means using something other than std:vector, and puts you in the domain of sparse vectors/matrixes.Streaky
You can even use boost::optional<T> instead of the child pointer. Using boost::optional<T> means that you benefit of its stack-allocation. No heap is used thenGasconade
@Dan: was considering adding another template parameter to call something other than T's child constructor... didn't, to keep it simple. But yeah, that doesn't help if you want to pass the index down. Well, OP hasn't really stated any hard requirements yet...Quarto
You don't need "if (child)" in the destructor, deleting a null value is ok.Quixote
@GMan: Yes, you're right. Odd how nobody complained that it wouldn't compile due to misuse of typename though ;) It's fixed now, insofar as it now compiles.Quarto
Also, a copy constructor needed for this custom solution.Degauss
What about making child mutable, so that the const methods will not return 0?Lapointe
@Thomas: mutable or const_cast can both get around the const-ness. I don't really like the idea of mutable, though... would you really expect an operation on a const object to allocate persistent memory?Quarto
@Alexander: Yes indeed, I'm not following the "Rule of Three" here. If I were using auto_ptr or boost::smart_ptr I wouldn't have to, either ;)Quarto
with auto_ptr, you would have to :) auto_ptr isn't copy constructible so you would need your own copy constructor and assignment operator. I would recommend shared_ptr in this case. Btw, i really like this &**this trickery. Neat :)Gasconade
@ephemient: I agree with Thomas that child should be declared mutable. This is exactly the kind of problem mutable was designed to solve. I mean if you desire pure constness, then a class like this would be a bad design anyway and in this case I would even argue it's incorrect!Spindling
G
10

Using boost::optional, you can have such a thing:

// 100 lazy BigStuffs
std::vector< boost::optional<BigStuff> > v(100);
v[49] = some_big_stuff;

Will construct 100 lazy's and assign one real some_big_stuff to v[49]. boost::optional will use no heap memory, but use placement-new to create objects in a stack-allocated buffer. I would create a wrapper around boost::optional like this:

template<typename T>
struct LazyPtr {
    T& operator*() { if(!opt) opt = T(); return *opt; }
    T const& operator*() const { return *opt; }

    T* operator->() { if(!opt) opt = T(); return &*opt; }
    T const* operator->() const { return &*opt; }    
private:
    boost::optional<T> opt;
};

This now uses boost::optional for doing stuffs. It ought to support in-place construction like this one (example on op*):

T& operator*() { if(!opt) opt = boost::in_place(); return *opt; }

Which would not require any copy-ing. However, the current boost-manual does not include that assignment operator overload. The source does, however. I'm not sure whether this is just a defect in the manual or whether its documentation is intentionally left out. So i would use the safer way using a copy assignment using T().

Gasconade answered 18/5, 2009 at 16:30 Comment(2)
vector<LazyPtr<Box> > v(100) will use 100*sizeof(Box), which is maybe okay but maybe OP doesn't want to use memory for Boxes that aren't allocated. Since OP hasn't described more requirements, we don't know...Quarto
Right, I dont want to waste space on not allocated objects.Degauss
S
2

I've never heard of such a thing, but then again there are lots of things I've never heard of. How would the "lazy pointer" put useful data into the instances of the underlying class?

Are you sure that a sparse matrix isn't what you're really looking for?

Streaky answered 18/5, 2009 at 15:6 Comment(1)
Because a sparse matrix fills a similar (though not identical) need. Note that the poster's example shows a vector of "lazy pointers"; this sounds a lot like a sparse matrix.Streaky
S
0

So far as I know, there's no existing implementation of this sort of thing. It wouldn't be hard to create one though.

Symposiarch answered 18/5, 2009 at 15:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.