簡體   English   中英

std::function 和 std::bind 做動態內存分配嗎?

[英]do std::function and std::bind do dynamic memory allocation?

如果我這樣做:-

class Thing  
{
    ...
    void function (const std::string& message);
};

std::list<std::function<void()>> work;

並且在“Thing”的某個成員中

work.push_back(std::bind(&Thing::function, this, "Hello"));

調用 std::bind 或使用 std::function<> 是否會導致使用 new 或其他方式進行動態內存分配? 或者所有的存儲都是在編譯時分配的? 如果標准沒有說明,那么在 Visual Studio 2012 中呢,因為我的程序只需要在那里構建,為了效率,我可能需要在我考慮使用這種機制的地方避免動態內存分配。

標准沒有規定,但總的來說,很容易看出std::function至少在某些情況下必須分配內存:

struct huge { char c[10000]; };
void foo(const huge &);
std::function<void()>{std::bind(foo, huge{})};

另一方面,至少在某些情況下,它可以通過將其函數對象放置在function對象足跡內的預分配緩沖區內來避免分配; 顯然有一個權衡,因為這可能會使其他用途占用更多的堆棧內存。 一個好的實現將能夠在將原始函數指針存儲在function對象中時避免內存分配,也可能用於mem_fn ,但它不太可能為bind這樣做。

例如,libstdc++ (g++) 內聯(函子)對象指針、函數指針和(非虛擬)成員函數指針,以及任何其他適合相同占用空間的東西,例如無狀態函子( union _Nocopy_types )。

如果可以,通過反轉控制流以接受模板化的函子對象而不是function您可以避免任何額外的內存分配:

template<typename F>
void my_algorithm(const F &);
my_algorithm(std::bind(foo, huge{}));

我只是在 g++ 的情況下對此進行了一些研究。

當談到 std::function 和動態內存分配時,有兩個關鍵點。

  1. std::function 可以存儲任意大小的對象,這意味着它在某些情況下必須執行動態內存分配。
  2. 對於某些類型, std::function 保證不會拋出異常。 這意味着它必須在沒有動態內存分配的情況下存儲某些類型。

gccs libstd+++ 中 std::function 的實現將在沒有動態內存分配的情況下存儲其他大小/對齊要求小於或等於它必須存儲的內容的大小/對齊要求的內容。

在沒有動態內存分配的情況下,它必須存儲的最大內容是指向成員函數的指針。 在基於“itanium c++ ABI”* 的編譯器上,這是普通指針大小的兩倍 因此,您可以在 g++ 中的 std::function 中存儲最多兩個大小的指針,而不會觸發動態內存分配。

據我所知,std::bind 只是將內容連接到一個對象中,因此將任何內容綁定到成員函數將導致一個大小至少為三個指針的對象。 將此對象分配給 std::function 將導致動態內存分配。

更好的選擇是使用 lambda。 這是靜態引用成員函數,為您提供最多捕獲兩個指針的空間,而不會觸發動態內存分配。

為了演示,我根據您的代碼松散地編寫了一些測試代碼。 我擺脫了字符串和列表,並使用了一個 const char *(以避免與 std::string 相關的內存分配)和放置 new(此代碼僅用於構建,而不是運行),並將其輸入到 Godbolt 中。

#include <functional>
using namespace std;

class Thing  
{
    void foo();
    void bar();
    void function (const char * message);
};

char baz[1024];

void Thing::foo() {
    new (baz) std::function<void()>(std::bind(&Thing::function, this, "Hello"));
}


void Thing::bar() {
    const char * s = "Hello";
    new (baz) std::function<void()>([this,s](){function(s);});
}

結果是。

Thing::foo():
        mov     r3, #0
        push    {r4, r5, r6, lr}
        ldr     r4, .L34
        mov     r6, r0
        sub     sp, sp, #16
        mov     r0, #16
        str     r3, [r4, #8]
        bl      operator new(unsigned int)
        ldr     r2, .L34+4
        mov     r1, #0
        mov     r3, r0
        str     r2, [sp]
        mov     r2, sp
        ldr     r5, .L34+8
        ldr     lr, .L34+12
        ldr     ip, .L34+16
        str     r1, [sp, #4]
        str     r6, [r0, #12]
        str     r0, [r4]
        str     r5, [r3, #8]
        ldm     r2, {r0, r1}
        str     lr, [r4, #12]
        stm     r3, {r0, r1}
        str     ip, [r4, #8]
        add     sp, sp, #16
        pop     {r4, r5, r6, pc}
        ldr     r3, [r4, #8]
        cmp     r3, #0
        beq     .L27
        ldr     r1, .L34
        mov     r2, #3
        mov     r0, r1
        blx     r3
.L27:
        bl      __cxa_end_cleanup
.L34:
        .word   .LANCHOR1
        .word   Thing::function(char const*)
        .word   .LC0
        .word   std::_Function_handler<void (), std::_Bind<void (Thing::*(Thing*,     char const*))(char const*)> >::_M_invoke(std::_Any_data const&)
        .word   std::_Function_base::_Base_manager<std::_Bind<void (Thing::*(Thing*, char const*))(char const*)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation)
Thing::bar():
            ldr     r2, .L38
            sub     sp, sp, #8
            stm     sp, {r0, r2}
            add     r2, sp, #8
            ldr     r3, .L38+4
            ldmdb   r2, {r0, r1}
            ldr     ip, .L38+8
            ldr     r2, .L38+12
            stm     r3, {r0, r1}
            str     ip, [r3, #12]
            str     r2, [r3, #8]
            add     sp, sp, #8
            bx      lr
    .L38:
            .word   .LC0
            .word   .LANCHOR1
            .word   std::_Function_handler<void (), Thing::bar()::{lambda()#1}>::_M_invoke(std::_Any_data const&)
            .word   std::_Function_base::_Base_manager<Thing::bar()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<Thing::bar()::{lambda()#1}> const&, std::_Manager_operation)

我們可以清楚地看到在 bind 情況下有內存分配,但在 lambda 情況下沒有。

* 盡管 g++ 和 clang++ 在許多不同的體系結構中使用了這個名稱。

我不確定這一點。 我想,正如 ecatmur 所建議的,這取決於該平台的 std 實現。 對於類似的問題,我使用代碼項目中的這個實現取得了很好的成功。 它支持很多平台。 有很好的文檔記錄,沒有動態內存分配。

http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible

應避免在游戲或模擬中運行時的通用動態內存分配。 問題並不總是碎片化或大瓶頸(這都是要避免的有效理由),而且還在於時間量通常是不確定的。 更特定於域的內存分配策略(例如“池化”或“成幀”)在這里將是有利的。

http://g.oswego.edu/dl/html/malloc.html

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM