首页 > 解决方案 > 需要使用 shared_ptr 吗?

问题描述

我有两个类:Lattice 和 ModelGUI。我想将函数从 Lattice 作为回调传递给 GUI。我将 Lattice 实现为 unique_ptr。一些代码:

模型GUI.h:

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

主.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 ,除非它们是完全必要的。

标签: c++callbacksmart-pointers

解决方案


你需要shared_ptr吗?

不,至少对于给定的例子来说不是。

如果latticegui被定义在不同的范围内,具有不同的生命周期并被广泛使用,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一下以使用 aunique_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;一开始使用更多?我们真的需要一个指针吗?

但是现在让我们先去吧,因为我们至少可以在我们前往更绿色的牧场之前让它看起来更漂亮。这与 lambda 表达式相同

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

通常我不是“Lambda 比 Lambda 更容易阅读”的拥护者bind,但这个案例是一个非常有说服力的论点。

回归本源,其实并没有什么不同

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

并完全消除了指针。没有指针,没有shared_ptr

如果t可以一劳永逸,唯一的用户就是回调,让 lambda 随身携带一份副本,t让原来的死掉。

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

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

}

注意mutable. Lambda 默认携带常量,不能用于调用非const方法或用作非const参数。


推荐阅读