Нужно использовать shared_ptr? - PullRequest
0 голосов
/ 31 мая 2019

У меня есть два класса: решетка и 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 настолько, насколько я могу, если только они не являются абсолютно необходимыми.

Ответы [ 2 ]

3 голосов
/ 31 мая 2019

Вам нужно 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 параметра.

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