У меня есть два класса: решетка и ModelGUI. Я хочу передать функцию из Lattice в GUI в качестве обратного вызова. Я реализовал Lattice как unique_ptr. Некоторый код:

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);

С этой реализацией я получил странные ошибки компиляции о шаблонах:

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>        ]

Но когда я использую shared_ptr вместо unique_ptr, все работает нормально. Это хорошая практика? Я слышал, чтобы избежать shared_ptr, насколько я могу, если они не являются абсолютно необходимыми.

0
Michael S. 31 Май 2019 в 00:29

2 ответа

Лучший ответ

Вам нужен shared_ptr?

По крайней мере, не для данного примера.

Если lattice и gui определены в разных областях с различной продолжительностью жизни и используются повсеместно, wowie-zowie, мы можем говорить о shared_ptr.

Почему?

Давайте начнем с очень простого примера, который показывает, почему unique_ptr вызывает горе.

#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 ничего не делает, кроме как сообщает нам, когда объект копируется, и доказывает, что функция запущена. Выполнив его, мы увидим, что t скопирован и выдает ожидаемый вывод из функции.

std::unique_ptr не может быть скопирован, потому что это в значительной степени разрушило бы всю уникальную часть описания задания. Мы видим, что если мы немного изменим main, чтобы использовать unique_ptr, и немного приблизимся к поставленному вопросу.

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

Как и ожидалось, это не компилируется. Мы можем сделать эту компиляцию, используя std::reference_wrapper

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

Или предоставить необработанный указатель на bind

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

Но для этого требуется tp иметь более широкий охват и гарантированно пережить f1. Что на самом деле сводится к тому, зачем использовать больше, чем test t;? Нам действительно нужен указатель здесь вообще?

Но давайте пойдем с этим сейчас, потому что мы можем, по крайней мере, сделать этот вид намного красивее, прежде чем отправиться на более зеленые пастбища. Вот то же самое с лямбда-выражением

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

Обычно я не сторонник «лямбда легче читать, чем bind», но этот случай - довольно убедительный аргумент.

Возвращаясь к основам, это не так уж сильно отличается от

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

И полностью устраняет указатель. Нет указателя, нет shared_ptr.

Если t может быть запущен и забыт, единственным пользователем является обратный вызов, пусть лямбда возьмет с собой копию t и позволит оригиналу умереть.

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

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

}

Обратите внимание на mutable. По умолчанию лямбда содержит константы и не может использоваться для вызова не-const методов или для использования в качестве не const параметра.

3
user4581301 31 Май 2019 в 01:01
  • Вы передаете std::unique_ptr по значению, и это всегда плохая идея, потому что у нее нет конструктора копирования.
  • Почему ваш код даже не компилируется? Похоже, что передача std :: unique_ptr связанной функции является долгоживущим ошибка визуальной студии.
0
gimme_danger 30 Май 2019 в 21:53