2016-12-20 18 views
12

Powodem, dla którego muszę o to zapytać, jest konieczność przechowywania std::function w wektorze, a wewnętrzny wektor, który mamy w firmie, w zasadzie wykonuje realloc, jeśli potrzebuje więcej pamięci . (Zasadniczo tylko memcpy, bez operatora kopiowania/przenoszenia)Dlaczego nie możemy trywialnie skopiować std :: function

Oznacza to, że wszystkie elementy, które możemy umieścić w naszym pojemniku, muszą być trywialnie kopiowalne.

Oto niektóre kodu do wykazania problematyczny egzemplarz miałem:

void* func1Buffer = malloc(sizeof(std::function<void(int)>)); 
std::function<void(int)>* func1p = new (func1Buffer) std::function<void(int)>(); 
std::function<void(int)>* func2p = nullptr; 
*func1p = [](int) {}; 
char func2Buffer[sizeof(*func1p)]; 
memcpy(&func2Buffer, func1p, sizeof(*func1p)); 
func2p = (std::function<void(int)>*)(func2Buffer); 
// func2p is still valid here 
(*func2p)(10); 
free(func1Buffer); 
// func2p is now invalid, even without std::function<void(int)> desctructor get triggered 
(*func2p)(10); 

rozumiem powinniśmy wspierać Kopiuj/przenieś elementu w celu przechowywania std::function bezpiecznie. Ale nadal jestem bardzo ciekawy, jaka jest bezpośrednia przyczyna nieprawidłowej kopii powyżej: std::function.

--------------------------------------------- ------- UpdateLine ------------------------------------------ ----------

Zaktualizowano przykład kodu.

Znalazłem bezpośredni powód tego błędu, poprzez debugowanie naszego wewnętrznego w domu więcej.

trywialnie kopiowane std::function ma pewną zależność od oryginalnej pamięci obiektu, usuń oryginalną pamięć będzie kosza źle skopiowany std::function nawet bez zniszczenie oryginalnego obiektu.

Dziękuję za odpowiedź wszystkich na ten wpis. To wszystko jest wartościowym wkładem. :)

+3

* Dlaczego nie możemy trywialnie skopiować std :: function *? Ponieważ standard definiuje to w ten sposób. –

+0

Dopóki używasz niezapisujących lambd, możesz uciec z przechowywaniem wskaźników funkcji w wektorze. – NathanOliver

+0

niektóre aktualizacje tutaj, zrobiłem jakiś błąd lokalnie i kod przykładowy podaję w moim pytaniu faktycznie działa. –

Odpowiedz

7

Problem polega na tym, jak należy wdrożyć std::function: musi zarządzać czasem życia dowolnego obiektu, który trzyma. Więc kiedy piszesz:

{ 
    std::function<Sig> f = X{}; 
} 

musimy wywołać destruktora X gdy f wykracza poza zakres. Ponadto, std::function [potencjalnie] przydzieli pamięć do przechowywania tego X, więc destruktor f musi również [potencjalnie] zwolnić tę pamięć.

Rozważmy teraz co się dzieje, gdy staramy się zrobić:

char buffer[100000]; // something big 
{ 
    std::function<void()> f = X{}; 
    memcpy(buffer, &f, sizeof(f)); 
} 
(*reinterpret_cast<std::function<void()>*>(buffer))(); 

W punkcie jesteśmy wywołanie funkcji „przechowywane” w buffer, obiekt X został już zniszczony, a pamięć trzymając go nie było [potencjalnie] uwolniony. Niezależnie od tego, czy X były TriviallyCopyable, nie mamy już numeru X. Mamy artystę znanego wcześniej pod nazwą X.

Bo to obowiązkiem std::function zarządzać własnych obiektów, to nie może być TriviallyCopyablenawet jeśli dodaliśmy wymóg, że wszystko udało się callables były TriviallyCopyable.


do pracy w realloc_vector, trzeba albo trzeba coś takiego function_ref (lub std::function<>*) (to jest typ, który po prostu nie posiada żadnych zasobów) lub trzeba zaimplementować własną wersję function że (a) zachowuje własną pamięć jako element członkowski, aby uniknąć przydzielania pamięci i (b) jest możliwe do skonstruowania tylko z podpowiedziami TriviallyCopyable, tak że sama staje się trywialnie kopiowalna. To, które rozwiązanie jest lepsze, zależy od tego, co faktycznie robi twój program.

4

Ale nadal jestem bardzo ciekawy, co jest bezpośrednią przyczyną nieprawidłowego std :: function copy above.

std::function nie może być TriviallyCopyable (lub warunkowo TriviallyCopyable), ponieważ w sposób ogólny na żądanie obiektu owijki nie można przyjąć, że składowany wywoływalny jest TriviallyCopyable.

Rozważ implementację własnej wersji std::function, która obsługuje tylko obiekty wywoływalne TriviallyCopyable (przy użyciu stałego bufora do przechowywania) lub użyj wektora wskaźników funkcji, jeśli ma to zastosowanie w twojej sytuacji.

+1

Problem nie polega na tym, że wszystko, co jest przechowywane, może nie być trywialnie kopiowalne, ale że musi przydzielić ten obiekt na stercie. – MikeMB

2

Funkcja std :: może przydzielić pamięć dla przechwyconych zmiennych. Podobnie jak w przypadku każdej innej klasy, która przydziela pamięć, nie można jej kopiować trywialnie.

+0

Nie tylko to, że pusty obiekt może również nie być "wielopoziomowy". –

3

Kopiowanie w sposób trywialny jest czymś nieodłącznie związanym z danym typem, a nie z obiektem.
Rozważmy następujący przykład:

#include<type_traits> 
#include<functional> 

int main() { 
    auto l = [](){}; 
    static_assert(not std::is_trivially_copyable<decltype(l)>::value, "!"); 

    std::function<void(void)> f; 
    bool copyable = std::is_trivially_copyable<decltype(f)>::value; 
    f = l; 

    // do something based on the 
    // fact that f is trivially copyable 
} 

Jak można wymusić właściwość Po przypisaniu do funkcji lambda, która nie jest trywialnie copyable?

To, czego szukasz, to maszyna wykonawcza, która podejmuje decyzję na podstawie rzeczywistego obiektu przypisanego do funkcji
To nie działa jak std::is_trivially_copyable.
Dlatego kompilator musi podjąć decyzję podczas kompilacji w odniesieniu do danej specjalizacji dla std::function. Ponieważ jest to ogólny pojemnik na obiekty do wywoływania i można przypisać mu obiekty kopiowane trywialnie, a także obiekty, które nie są trywialnie kopiowalne, reszta nie ulega wątpliwości.

Powiązane problemy