如何使用自定义分配器创建std :: function?

21

假设我有一个名为MyAlloc的自定义分配器,并已成功地将其与std::vector<int>一起使用,以节省一些代码,如下所示:

std::vector<int,MyAlloc<int>> vec;

现在我希望使用自定义分配器将lambda保存到std :: function中,我该怎么做?

我的尝试失败了:

int i[100];
std::function<void(int)> f(MyAlloc<void/*what to put here?*/>{},[i](int in){
    //...
});

更新: C++标准库中的std::function分配器已经被弃用


我在std::function中没有看到任何分配器支持。 - RedX
1
@RedX http://en.cppreference.com/w/cpp/utility/functional/function/function 表明这是可能的。 - odinthenerd
2
@RedX 文档没问题。请注意,PorkyBrain链接了 std::function 构造函数的文档,而你链接的是类本身。分配器仅在构造 std::function 时需要,而不是在使用它时需要。因此,只有构造函数对分配器类型进行了模板化,而不是类本身。 - ComicSansMS
2
@PorkyBrain 你在使用哪个编译器?我刚刚注意到VC似乎在这里搞乱了构造函数参数的顺序 - ComicSansMS
@ComicSansMS MSVC2013 - odinthenerd
1
有一个提案建议从std::function中删除分配器支持:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0302r0.html,其中列出了一些已知问题。 - Andriy Tylychko
2个回答

25

根据标准,您需要提供一个标签类型作为第一个参数,以表明您想使用自定义分配器:

std::function<void(int)> f(std::allocator_arg, MyAlloc<char>{}, [i](int in){
    //...
});

正如评论中 @Casey@Potatoswatter 所指出的那样,分配器模板参数提供的类型并不重要,只要它是一个对象类型。所以在这里使用char就可以了。

针对 C++17 的更新:原来的std::function分配器支持存在一些根本性问题,在C++17中已经被弃用。如果仍然坚持使用,请务必在使用之前仔细检查您的实现。 GCC的标准库从未实现过这些函数,但即使您的标准库实现了这些函数,它也可能不会按照您的预期运行。


谢谢你的帮助,它似乎在MSVC2013上运行正常,你确定MyAlloc的模板参数应该是void吗? - odinthenerd
1
@PorkyBrain:凭直觉来说,任何类型都可以,因为std::function重新绑定分配器到另一个(内部)类型。 - Matthieu M.
@PorkyBrain 几乎可以确定使用哪个分配器参数都无关紧要:std::function 将立即重新绑定分配器到其实际分配所需的任何内部类型,并且您传递给构造函数的分配器仅用于复制构造该重新绑定的分配器。 - Casey
3
根据C++11标准§[func.wrap.func.con]/1规定:“当调用任何第一个参数类型为allocator_arg_t的函数构造函数时,第二个参数应该具有符合Allocator要求的类型(Table 17.6.3.5)。如果需要,将使用分配器参数的副本来为构造的function对象内部数据结构分配内存。” 因此,任何符合要求的分配器类型都是适当的。 - Casey
2
void 不是一个有效的分配器类型参数。虽然 std::allocator<void> 被定义了,但是通常的分配器需要对象类型。由于任何随意的对象类型都可以使用,因此使用 char 更好。 - Potatoswatter
显示剩余3条评论

1
我知道这个问题已经得到了正确的回答,但是即使阅读了这篇文章和回复,我仍然在尝试重载std :: function的分配器时遇到了一些语法问题,这需要在X64、PS4和Xbox One上进行交叉编译,在VS2012中实现。
如果读者不清楚,您需要根据Casey的评论声明一个分配器类。虽然如果您阅读所有回复,这是相当明显的,但不清楚的是这些分配器是如何传递给对象的,它与我之前使用过的大多数STL分配器不同,后者将分配器类型(而不是实例)作为类型规范的一部分。
对于std :: function,构造函数提供了一个实例化的分配器,这就是ComicSansMS上面展示的内容。
对于使用成员函数而不是此示例中显示的lambda代码,情况会变得有点棘手:
   #include <functional>

    MyAllocType g_myAlloc; // declared somewhere and globally instantiated to persist

    // sample of a member function that takes an int parameter
    class MyClassType
    {
    public:
        void TestFunction( int param )
        {
        }
    };

    MyClassType MyClass; // instantiated object

    // example without allocator
    // note the pointer to the class type that must precede function parameters since 
    // we are using a method. Also std::mem_fn is require to compile in VS2012 :/
    std::function<void(MyClassType*, int)> f( std::mem_fn( &MyClassType::TestFunction ) );

    // usage of function needs an instantiated object (perhaps there is a way around this?)
    f( &MyClass, 10 );

    // example with allocator
    std::function<void(MyClassType*, int)> f(std::allocator_arg, g_myAlloc, std::mem_fn( &MyClassType::TestFunction ) );

    // usage of function is the same as above and needs an instantiated object 
    f( &MyClass, 10 );

    //or a non member function, which is much cleaner looking
    void NonMemberFunction( int param )
    {
    }

    std::function<void(int)> f(std::allocator_arg, g_myAlloc, NonMemberFunction);

希望这能帮助人们。对我来说,让它工作起来的时间比我想象的要长,因为我经常使用这个网站,所以我觉得我应该在这里留下评论,即使只是为了自己知道如何使用它。:)
对于那些比我聪明的人有两个最终问题:
问:有没有办法将分配器作为类型的一部分包含进去?
问:有没有办法在没有对象实例的情况下使用成员函数?
如果您决定将其中一个 std::function 对象作为参数传递给其他函数,则需要使用 std::function::assign 进行更新,否则赋值将导致浅层复制。如果您试图将其传递给具有比原始对象更长的生命周期的对象,则可能会出现问题。
例如:
std::function<void(MyClassType*, int)> f(std::allocator_arg, g_myAlloc, std::mem_fn( &MyClassType::TestFunction ) );

void FunctionTakeParam( std::function<void(MyClassType*, int)> &FunctionIn )
{
    // this results in a reallocation using your allocator
    std::function<void(MyClassType*, int)> MyLocalFunction.assign( std::allocator_arg, g_myAlloc, FunctionIn ); 

    // the below results in a shallow copy which will likely cause bad things
    //std::function<void(MyClassType*, int)> MyLocalFunction( std::allocator_arg, g_myAlloc, FunctionIn ); 

    ...
}

完全没有理由将分配器包含在类型中,你为什么要这样做呢?一个成员函数可以在没有对象实例的情况下传递,但是它不能在没有对象的情况下被调用,这甚至没有意义。例如:struct person { void raise_hand(){} }; 然后你调用 person::raise_hand()。谁举手了? - Mooing Duck
为什么?因为你可以有一个实用成员函数,它不访问任何成员,但恰好是类的一部分。或者一个静态函数,不是说这是典型的用例,我以前从未见过这种语法,很好奇。关于类型中的分配器,它是为了防止在开始传递这些对象时出现尴尬。我无法确定如果我通过值传递其中一个std::function对象,副本是否也会使用我的分配器?我认为可能不会,所以在类型声明中拥有它可以使使用更清晰。 - jeffdev
再次尝试回答为什么我想要它在类型中的原因,这是我如何重载 std::string 的分配器:typedef std::basic_string<char, std::char_traits<char>, STLHeapAllocator<char> > SampleHeapString;请注意,我实际上没有在任何地方实例化分配器。而我实例化的任何此类型的对象都使用我的分配器,对吗?或者我疯了,我做错了吗?这也是可能的 :) - jeffdev
3
哦,就这样?std::function(以及std::shared_ptr等几个类)只在堆栈上分配一个对象。因此,它唯一可能需要分配器的另一件事情是清除分配的内存。因此,它可以轻松地使用类型擦除,而不会增加额外开销。它仍然包含一个分配器的实例,但可以用另一个分配器给它另一个函数,它将在运行时切换分配器(没有额外开销)。因此,分配器的类型不会影响函数的类型。 - Mooing Duck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接