0

I have two classes: Lattice and ModelGUI. I want to pass function from Lattice to GUI as a callback. I implemented Lattice as unique_ptr. Some code:

ModelGUI.h:

using CheckTypeClbk = std::function<Enums::AgentType(int)>;
ModelGUI(const Matrix* matrix_, CheckTypeClbk callback, float windowHeight_, float windowWidth_, float latticeWidth_);

main.cpp:

std::unique_ptr<ILattice> lattice(new Lattice(5, qMap));
ModelGUI gui(lattice->getLattice(), std::bind(&ILattice::checkAgentType, lattice, std::placeholders::_1),
800, 1200, 800);

With this implementation I got strange compilation errors about templates:

1>main.cpp
1>d:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\xutility(390): error C2664: 'std::tuple<std::unique_ptr<ILattice,std::default_delete<_Ty>>,std::_Ph<1>>::tuple(std::tuple<std::unique_ptr<_Ty,std::default_delete<_Ty>>,std::_Ph<1>> &&)': cannot convert argument 1 from 'std::unique_ptr<ILattice,std::default_delete<_Ty>>' to 'std::allocator_arg_t'
1>        with
1>        [
1>            _Ty=ILattice
1>        ]
1>d:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\xutility(389): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
1>d:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\functional(1902): note: see reference to function template instantiation 'std::_Compressed_pair<Enums::AgentType (__cdecl ILattice::* )(int),std::tuple<std::unique_ptr<ILattice,std::default_delete<_Ty>>,std::_Ph<1>>,false>::_Compressed_pair<Enums::AgentType(__cdecl ILattice::* )(int),_Cv_TiD&,const std::_Ph<1>&>(std::_One_then_variadic_args_t,_Other1 &&,_Cv_TiD &,const std::_Ph<1> &)' being compiled
1>        with
1>        [
1>            _Ty=ILattice,
1>            _Cv_TiD=std::unique_ptr<ILattice,std::default_delete<ILattice>>,
1>            _Other1=Enums::AgentType (__cdecl ILattice::* )(int)
1>        ]
1>d:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\functional(1903): note: see reference to function template instantiation 'std::_Compressed_pair<Enums::AgentType (__cdecl ILattice::* )(int),std::tuple<std::unique_ptr<ILattice,std::default_delete<_Ty>>,std::_Ph<1>>,false>::_Compressed_pair<Enums::AgentType(__cdecl ILattice::* )(int),_Cv_TiD&,const std::_Ph<1>&>(std::_One_then_variadic_args_t,_Other1 &&,_Cv_TiD &,const std::_Ph<1> &)' being compiled
1>        with
1>        [
1>            _Ty=ILattice,
1>            _Cv_TiD=std::unique_ptr<ILattice,std::default_delete<ILattice>>,
1>            _Other1=Enums::AgentType (__cdecl ILattice::* )(int)
1>        ]
1>d:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\functional(1902): note: while compiling class template member function 'std::_Binder<std::_Unforced,Enums::AgentType (__cdecl ILattice::* )(int),std::unique_ptr<ILattice,std::default_delete<_Ty>> &,const std::_Ph<1> &>::_Binder(_Fx &&,std::unique_ptr<_Ty,std::default_delete<_Ty>> &,const std::_Ph<1> &)'
1>        with
1>        [
1>            _Ty=ILattice,
1>            _Fx=Enums::AgentType (__cdecl ILattice::* )(int)
1>        ]
1>d:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\functional(1929): note: see reference to function template instantiation 'std::_Binder<std::_Unforced,Enums::AgentType (__cdecl ILattice::* )(int),std::unique_ptr<ILattice,std::default_delete<_Ty>> &,const std::_Ph<1> &>::_Binder(_Fx &&,std::unique_ptr<_Ty,std::default_delete<_Ty>> &,const std::_Ph<1> &)' being compiled
1>        with
1>        [
1>            _Ty=ILattice,
1>            _Fx=Enums::AgentType (__cdecl ILattice::* )(int)
1>        ]
1>d:\predator-prey\predator-prey\main.cpp(16): note: see reference to class template instantiation 'std::_Binder<std::_Unforced,Enums::AgentType (__cdecl ILattice::* )(int),std::unique_ptr<ILattice,std::default_delete<_Ty>> &,const std::_Ph<1> &>' being compiled
1>        with
1>        [
1>            _Ty=ILattice
1>        ]

But when I am using shared_ptr instead of unique_ptr, everything works fine. Is a good practice? I've heard to avoid shared_ptr as much as I can, unless they are totally necessary.

Michael S.
  • 135
  • 1
  • 11
  • The thing with a `unique_ptr` is there can be only one. You can't copy them. You can't pass them around without transferring ownership. You have to decide who is the rightful owner of the pointer. Only if `lattice` and `gui` both have a legitimate stake should you use a `shared_ptr`. In this case it looks like `lattice` has a wider scope than `gui` and can safely maintain ownership and you can provide a raw pointer to `gui`. Sidenote: Instead of `std::bind`, consider using a lambda expression. – user4581301 May 30 '19 at 21:45
  • What's wrong with `std::bind`? – Michael S. May 30 '19 at 21:46
  • `std::bind` is generally slower and more cumbersome than a lambda, and many find the lambda syntax cleaner. – user4581301 May 30 '19 at 21:50
  • Since I had to grind this out for a comment to the answer, here's what that simplified version would look like with a lambda: https://ideone.com/ErgtSi . Note the `unique_ptr` `t` is being passed by reference. Do not allow `f2` to outlive `t`. – user4581301 May 30 '19 at 22:22

2 Answers2

3

Do you need shared_ptr?

No. At least not for the given example.

If lattice and gui are defined in different scopes with different lifespans and being used all over the place, wowie-zowie, we can talk about shared_ptr.

Why?

Let's start with a really simple example that shows why unique_ptr is causing grief.

#include <functional>
#include <iostream>

struct test
{
    test() = default;
    test(const test &)
    {
        std::cout << "copied" << std::endl;
    }
    void func(int i)
    {
        std::cout << i << std::endl;
    }
};

int main()
{
    test t;
    std::function<void(int)> f1 = std::bind(&test::func, t, std::placeholders::_1);
    f1(1);
}

test doesn't do much other than tell us when the object is copied and prove that the function ran. Executing it we'll see that t is copied and produced the expected output from the function.

std::unique_ptr cannot be copied because that would pretty much ruin the whole unique part of the job description. We see that if we change main a little to use a unique_ptr and get a little closer to the question as posed.

int main()
{
    std::unique_ptr<test> tp = std::make_unique<test>();
    std::function<void(int)> f1 = std::bind(&test::func, tp, std::placeholders::_1);
}

As expected, this doesn't compile. We can make this compile by using a std::reference_wrapper

std::function<void(int)> f1 = std::bind(&test::func, std::reference_wrapper<std::unique_ptr<test>>(tp), std::placeholders::_1);

or provide a raw pointer to bind

std::function<void(int)> f1 = std::bind(&test::func, tp.get(), std::placeholders::_1);    f1(1);

but this requires tp to have wider scope and be guaranteed to outlive f1. What this really comes down to is why use more than test t; in the first place? Do we really need a pointer here at all?

But let's go with it for now because we can at least make this look a lot prettier before we head off to greener pastures. Here is the same thing with a lambda expression

std::function<void(int)> f1 = [&tp](int i) { tp->func(i); };

Normally I'm not a champion of "Lambda is easier to read than bind," but this case is a pretty convincing argument.

Getting back to basics, it's not really all that different from

int main()
{
    test t;
    std::function<void(int)> f1 = [&t](int i) { t.func(i); };
    f1(1);
}

and completely eliminates the pointer. No pointer, no shared_ptr.

If t can be fire and forget, the only user is the callback, let the lambda carry around a copy of t and let the original die.

std::function<void(int)> scopedemo()
{
    test t;
    return [t](int i) mutable { t.func(i); }; //
}

int main()
{
    auto f1 = scopedemo();
    f1(1);

}

Note the mutable. Lambda defaults to carrying around constants and can't be used to invoke non-const methods or be used as a non-const parameter.

Community
  • 1
  • 1
user4581301
  • 29,019
  • 5
  • 26
  • 45
0
  • You are passing std::unique_ptr by value and it's always a bad idea, because it doesn't have a copy constructor.
  • Why doesn't your code even compile? It seems that passing std::unique_ptr to a bound function is a long-living bug of visual studio.
gimme_danger
  • 740
  • 1
  • 4
  • 14
  • This is more a comment than an answer. The question raised in your second point is answered by the first point. – user4581301 May 30 '19 at 21:58
  • Nope, these two points are mostly ortogonal. Because this code compiles in gcc and clang, BUT fails exactly in vs2012+. – gimme_danger May 30 '19 at 22:06
  • 1
    `std::bind` takes its arguments by value. That forces a copy. `unique_ptr` cannot be copied. [Error results.](https://ideone.com/fOVt45) – user4581301 May 30 '19 at 22:17