Question

I'm using boost-variant, and when switching types in the variant I want to ensure the destructor is called. The following code "works" but i'm not sure why. I feel like it should segfault as it's calling a delete on an uninitialized pointer. Is there some boost-variant magic going on behind the scenes?

#include <iostream>
#include <boost/variant.hpp>
using namespace std;

class A
{
    public:
        A() {}
        virtual ~A() { cout << "Destructing A" << endl; }
};

class B
{
    public:
        B() {}
        virtual ~B() { cout << "Destructing B" << endl; }
};

typedef boost::variant<A*, B*> req;

class delete_visitor : public boost::static_visitor<void>
{
    public:
        inline void operator() (A *a) const
        {
            cout << "Will destruct A" << endl;
            delete a;
        }
        inline void operator() (B *b) const
        {
            cout << "Will destruct B" << endl;
            delete b;
        }
};
class Wrapper
{
    public:
        Wrapper(int s) {
            setBackend(s);
        }
        virtual ~Wrapper() {
            // cleanup
            boost::apply_visitor(delete_visitor(), my_pick);
        }
        void setBackend(int s)
        {
            // make sure if we already have put something in our variant, we clean it up
            boost::apply_visitor(delete_visitor(), my_pick);
            if(s == 0)
                my_pick = new A();
            else
                my_pick = new B();
        }

    private:
        req my_pick;
};

int main()
{
    Wrapper *w = new Wrapper(0);
    w->setBackend(1);
    delete w;
    return 0;
}

The following is what I get for output:

Will destruct A
Will destruct A
Destructing A
Will destruct B
Destructing B
Was it helpful?

Solution

According to Boost doc for boost::variant:

"Never-Empty" Guarantee

All instances v of type variant guarantee that v has constructed content of one of the types Ti, even if an operation on v has previously failed.

Looking in "boost/variant.hpp", in particular variant's default constructor, you see:

// boost/variant.hpp: 1383
variant()
{
    // NOTE TO USER :
    // Compile error from here indicates that the first bound
    // type is not default-constructible, and so variant cannot
    // support its own default-construction.
    //
    new( storage_.address() ) internal_T0();
    indicate_which(0); // zero is the index of the first bounded type
}

For the variant types being bounded, the first type gets default-init. That means for your req type, A * gets zero-init. That also means B * is zero-init since variants can be viewed as a union.

OTHER TIPS

Calling delete on an uninitialized pointer is Undefined Behavior. The fact that it compiles doesn't make the code legal. In any case, though, I think you should be using memory management for this kind of thing:

typedef boost::variant<boost::shared_ptr<A>, boost::shared_ptr<B>> req;

// ....

if (s == 0)
    my_pick = boost::make_shared<A>();
else
    my_pick = boost::make_shared<B>();

Calling delete on an uninitialized pointer is undefined behaviour, which means anything can happen, including nothing at all.

Especially if the uninitialized pointer happens to be in memory that hadn't been used previously, it is not unlikely that this memory will contain zero, so delete gets a null pointer and does nothing.

The second likely(!) result is that you get a segfault as you expected, because the pointer happens to lie in memory that doesn't contain a valid pointer value.

Other possibilities are: The pointer happens to be at a place containing the address of a completely unrelated object, destroying that (likely while calling the completely wrong destructor). Or the pointer points into the heap, but somewhere in the middle, and you'll corrupt the internal heap structure, causing mysterious crashes much later.

That list is by no means exhaustive.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top