views:

1070

answers:

5

I need a shared_ptr like object, but which automatically creates a real object when I try to access it's 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?

+2  A: 

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?

Dan Breslau
Why did you point to the sparse matrix?
Mykola Golubyev
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.
Dan Breslau
A: 

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

Head Geek
A: 

I'm not completely sure, but I assume that some std::string implementations have copy on write behaviour. If that is what you're looking for, check out the source. However instead of all lazy objects you would have 1 object with 99 references to it and 1 copied object.

stefaanv
+13  A: 

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;
ephemient
it will make sense to contain child as auto_ptr
Mykola Golubyev
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.
Dan Breslau
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 then
Johannes Schaub - litb
@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...
ephemient
You don't need "if (child)" in the destructor, deleting a null value is ok.
GMan
@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.
ephemient
Also, a copy constructor needed for this custom solution.
Alexander Artemenko
What about making child mutable, so that the const methods will not return 0?
Thomas L Holaday
@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?
ephemient
@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 ;)
ephemient
Johannes Schaub - litb
+6  A: 

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().

Johannes Schaub - litb
`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...
ephemient
You're right, that's a good point :)
Johannes Schaub - litb
Right, I dont want to waste space on not allocated objects.
Alexander Artemenko