Boost logo

Boost Users :

From: Yuval Ronen (ronen_yuval_at_[hidden])
Date: 2005-07-17 04:41:50


<Bjorn.Karlsson_at_[hidden]> wrote in message
news:3D8559AE95B4D611B02C0002557C6C8B01A1AB75_at_STH-EXCH...
> > From: Felipe Magno de Almeida [mailto:felipe.m.almeida_at_[hidden]]
> >
> > What I wanted is a vector of variant<object1, object2> and I dont want
> > object1 and object2 to be copied because it is expensive. But Im not
> > figuring out how to do this... tried boost::ref either with no
> > success... :/

Funny I had the same problem just a few weeks ago...

> Boost.Smart_ptr (http://www.boost.org/libs/smart_ptr/smart_ptr.htm) can
> help: use boost::shared_ptr, which is a reference-counted smart pointer.
>
> typedef boost::variant<object1,object2> variant_type;
>
> std::vector<boost::shared_ptr<variant_type> > vec;
> boost::shared_ptr<variant_type> v(new variant_type(object1()));
> vec.push_back(v); // No copying of the variant

I'm afraid this is not perfect. Copy will still occur when creating the
variant. The variant's constructor copies the parameter into its own
storage. It's just how it works. If object1/object2 were non-copyable, then
you'll see that this code doesn't compile.

The solution I found starts with something similar to this, but slightly
different. Instead of:
    typedef boost::variant<object1,object2> variant_type;
    std::vector<boost::shared_ptr<variant_type> > vec;
I wrote:
    typedef
boost::variant<boost::shared_ptr<object1>,boost::shared_ptr<object2> >
variant_type;
    std::vector<variant_type> vec;

Only this ensures that no copies of object1/object2 will be made.
However, this solution has two problems.
The first problem is that shared_ptr can be NULL, and our logic forces the
shared_ptr to never be NULL. So I wrote the following class:

template <typename T>
class shared_ref
{
public:
    typedef T value_type;

    template <typename Y>
    explicit shared_ref(Y *a_ptr) : m_ptr(a_ptr) { }
    template <typename Y>
    explicit shared_ref(std::auto_ptr<Y> a_ptr) : m_ptr(a_ptr) { }

    T& get() const
    {
        T *ptr = m_ptr.get();
        assert(ptr); // that's the entire essence of this class
        return *ptr;
    }

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

private:
    boost::shared_ptr<T> m_ptr;
};

This class is like shared_ptr, but it's never NULL, so I called it
shared_ref. The non-null contract is validated in run-time using assert (in
the get() function). (BTW, for completeness, I should have added static_cast
and dynamic_cast constructors, but I had no immediate use for it, so
laziness took control).
Thanks to the non-null contract, I could add the implicit conversion
operator to T&.

The shared_ref solved this first problem, but the second problem is that
that shared_ptr/shared_ref use ptr/ref const semantics (a const
shared_ref<T> doesn't mean that T is const, only shared_ref<const T> does),
and not value const semantics. So I wrote another class:

template <typename T>
class shared_obj
{
public:
    typedef T value_type;

    explicit shared_obj(T *a_ptr) : m_ref(a_ptr) { }
    explicit shared_obj(std::auto_ptr<T> a_ptr) : m_ref(a_ptr) { }
    shared_obj(const shared_ref<T> &a_ref) : m_ref(a_ref) { }

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

private:
    shared_ref<T> m_ref;
};

which gives value const semantics - a const shared_obj<T> means that T is
const.

Now I can write
    typedef
boost::variant<boost::shared_obj<object1>,boost::shared_obj<object2> >
variant_type;

The value const semantics, plus the implicit [const] T& converstion
operators allow you to use visitors as if it was a regular variant:

class my_visitor : public boost::static_visitor<some_type>
{
    some_type operator()([const] object1 &o1) { ... }
    some_type operator()([const] object2 &o2) { ... }
};

Of course, this solution is a workaround to the fact that variant can't
handle non-copyable objects. What I would *really* like to see, is variant
support for non-copyable classes, something like boost::optional's in-place
factory. Unfortunately, the varaint maintainers seem to not follow these
lists lately...

HTH,
Yuval


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net