11

I have the following example:

#include <iostream>
#include <functional>

struct Tmr {
    typedef std::function<void(void)> Callback;
    Callback cb;

    Tmr(Callback cb_) :
        cb( cb_ )
    {
    }

    void timeout()
    {
        cb();
    }
};

struct Obj {
    struct Tmr t;

    Obj() :
        t( std::ref( *this ) )
    {
    }

    void operator () ()
    {
        std::cout << __func__ << '\n';
    }
};

int main(int argc, char *argv[])
{
    Obj o;

    o.t.timeout();

    return 0;
}

This runs fine, but initially I had the constructor of Obj as:

Obj() :
    t( *this )

Which results in a runtime error. I guess this is because only a reference to the member function is stored in my callback, and not the object to call the member on.

What I don't understand is what std::ref does when I do Obj() : t(std::ref(*this)) and why this makes the program work. Can anyone shed some light on what's going on and how it works ?

Athafoud
  • 2,898
  • 3
  • 40
  • 58
binary01
  • 1,728
  • 2
  • 13
  • 27
  • No `Obj() : t(*this)` works fine. Your `Callback` is a functor type, which your `Obj` also is. – Jean-Baptiste Yunès Jan 16 '16 at 11:27
  • @Jean-BaptisteYunès Are you saying the compiler generates invalid code, since `Obj() : t(*this)` crashes at runtime , but it should work fine ? – binary01 Jan 16 '16 at 11:28
  • 2
    It works on my g++ std c++11 compiler, and I can't see why it doesn't work for you. – Jean-Baptiste Yunès Jan 16 '16 at 11:29
  • Does it help if you mark `operator()` as `const`? – Alan Stokes Jan 16 '16 at 11:30
  • What runtime error do you get? – Alan Stokes Jan 16 '16 at 11:30
  • See here: http://stackoverflow.com/questions/24336790/functor-reference-through-a-stdfunction – Dai Jan 16 '16 at 11:30
  • Whatever, `std::ref` is a wrapper that construct an object to encapsulate a reference to some object. It is used when you want to store a reference in a container for example (which you normally can't). – Jean-Baptiste Yunès Jan 16 '16 at 11:31
  • @Jean-BaptisteYunès It does NOT work on my g++ 5.3.1/linux 32 bit nor in VS 2015 (Note that the whole example, as mentioned, works fine.) Did you change the constructor so it reads `Obj() : t(*this)` instead of `Obj() : t(std::ref(*this))` ? – binary01 Jan 16 '16 at 11:31
  • @AlanStokes No, marking operator() as const doesn't change anything. It generates a Illegal instruction (core dumped) on linux and a 0xC0000005: Access violation reading location 0xCCCCCCCC. in MSVC – binary01 Jan 16 '16 at 11:36
  • 1
    As `*this` is under construction at that time, is it really surprising that its value can be invalid ...? – Ilya Jan 16 '16 at 12:09
  • @Ilya There are numerous question here on SO, including http://stackoverflow.com/questions/869281/passing-this-from-constructor-initializer-list , that says it would be fine. Or are those answers wrong, or is there something with my code here that makes them not apply ? – binary01 Jan 16 '16 at 12:33
  • 1
    @binary01 you're not passing `this`, you're passing `*this` – Ilya Jan 16 '16 at 12:51

2 Answers2

5

When you don't pass by reference you're copying *this before t has been initialised - which means you're copying t and its callback member before they've been initialised, which is undefined behaviour.

(And the copy constructor of std::function is likely to try to copy what's pointed to by an uninitialised pointer, which is what causes the actual crash.)

Alan Stokes
  • 18,815
  • 3
  • 45
  • 64
1

You code crashes because of copying of uninitialized Callback object. You can see a sequence of event below:

1. Copy constructor of Obj is called in t(*this)
2. Copy constructor of Tmr is called as t is a member of Obj
3. Copy constructor of Callback is called as cb is a member of Tmr
4. Execution fails while trying to copy from uninitialized Callback object.

By using std::ref you bypass a copy creation of Obj; that's why it doesn't crash.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
sliser
  • 1,645
  • 11
  • 15