Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

what's the difference between the function pointer getting from std::function::target<>() and normal function pointer?

Tags:

c++

xcode

The following code is a signal implementation copied from APUE with a little modification

namespace
{
    using signal_handler = void (*)(int);
    signal_handler signal(sigset_t sig, signal_handler);
}

Signal::signal_handler Signal::signal(sigset_t sig, void (*handler)(int)) 
{
    struct sigaction newAction, oldAction;

    sigemptyset(&newAction.sa_mask);
    newAction.sa_flags = 0;
    newAction.sa_handler = handler;

    if (sig == SIGALRM) 
    {
#ifdef SA_INTERRUPT
        newAction.sa_flags |= SA_INTERRUPT;
#endif
    }
    else 
    {
        newAction.sa_flags |= SA_RESTART;
    }

    if (sigaction(sig, &newAction, &oldAction) < 0)
        throw std::runtime_error("signal error: cannot set a new signal handler.")

    return oldAction.sa_handler;
}

The above code works fine during my test, but I wanted to make it more like a C++ code, so I changed signal_handler alias to

using signal_handler = std::function<void (int)>;

and also I use

newAction.sa_handler = handler.target<void (int)>();

to replace

newAction.sa_handler = handler;

and now there is a problem. I find newAction.sa_handler is still NULL after

newAction.sa_handler = handler.target<void (int)>();

but I don't know why. Anyone can help me explain this? thanks. Here is my test code:

void usr1_handler(int sig) 
{
    std::cout << "SIGUSR1 happens" << std::endl;
}

void Signal::signal_test() 
{
    try 
    {
        Signal::signal(SIGUSR1, usr1_handler);
    } 
    catch (std::runtime_error &err) 
    {
        std::cout << err.what();
        return;
    }

    raise(SIGUSR1);
}

Even when using the original code when I run it in Xcode, there is no output. Instead, I run the executable file manually, I can see SIGUSR1 happens in the terminal. Why? How can I see the output using Xcode?

like image 506
Sherwin Avatar asked Apr 28 '16 01:04

Sherwin


1 Answers

The direct answer is that target() is very picky - you must name the type of the target exactly to get a pointer to it, otherwise you get a null pointer. When you set your signal to usr1_handler, that is a pointer to a function (not a function) - its type is void(*)(int), not void(int). So you're simply giving the wrong type to target(). If you change:

handler.target<void (int)>();

to

handler.target<void(*)(int)>();

that would give you the correct target.

But note what target() actually returns:

template< class T >
T* target();

It returns a pointer to the provided type - in this case that would be a void(**)(int). You'd need to dereference that before doing further assignment. Something like:

void(**p)(int) = handler.target<void(*)(int)>();
if (!p) {
    // some error handling
}
newAction.sa_handler = *p;

Demo.


However, the real answer is that this makes little sense to do. std::function<Sig> is a type erased callable for the given Sig - it can be a pointer to a function, a pointer to a member function, or even a wrapped function object of arbitrary size. It is a very generic solution. But sigaction doesn't accept just any kind of generic callable - it accepts specifically a void(*)(int).

By creating a signature of:

std::function<void(int)> signal(sigset_t sig, std::function<void(int)> );

you are creating the illusion that you are allowing any callable! So, I might try to pass something like:

struct X {
    void handler(int ) { ... }
};

X x;
signal(SIGUSR1, [&x](int s){ x.handler(s); });

That's allowed by your signature - I'm providing a callable that takes an int. But that callable isn't convertible to a function pointer, so it's not something that you can pass into sigaction(), so this is just erroneous code that can never work - this is a guaranteed runtime failure.

Even worse, I might pass something that is convertible to a function pointer, but may not know that that's what you need, so I give you the wrong thing:

// this will not work, since it's not a function pointer
signal(SIGUSR1, [](int s){ std::cout << s; }); 

// but this would have, if only I knew I had to do it
signal(SIGUSR1, +[](int s){ std::cout << s; }); 

Since sigaction() limits you to just function pointers, you should limit your interface to it to just function pointers. Strongly prefer what you had before. Use the type system to catch errors - only use type erasure when it makes sense.

like image 186
Barry Avatar answered Nov 06 '22 01:11

Barry