Почему мы не можем тривиально копировать std:: function

Причиной для этого является необходимость хранить std::function в векторе, а собственный вектор, который мы имеем в компании, в основном делает realloc, если ему требуется больше памяти. (В основном просто memcpy, оператор копирования/перемещения не участвует)

Это означает, что весь элемент, который мы можем положить в наш контейнер, должен быть тривиально-скопирован.

Вот какой код для демонстрации проблемной копии, которую я имел:

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);

Я понимаю, что мы должны поддерживать копирование/перемещение элемента, чтобы сохранить std::function безопасно. Но мне все еще очень любопытно, что является прямой причиной недействительной копии std::function выше.

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

Обновлен образец кода.

Я нашел прямую причину этой неудачи, отлаживая наш внутренний вектор больше.

Тривиально скопированный std::function имеет некоторую зависимость от исходной памяти объекта, удаление исходной памяти приведет к удалению сильно скопированного std::function даже без уничтожения исходного объекта.

Спасибо всем за ответ на этот пост. Это все ценный вклад.:)

Ответ 1

Проблема заключается в том, как std::function должен быть реализован: он должен управлять временем жизни любого объекта, на котором он держится. Поэтому, когда вы пишете:

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

мы должны вызывать деструктор X, когда f выходит за рамки. Более того, std::function [потенциально] выделяет память для хранения этого X, поэтому деструктор f должен также [потенциально] освобождать эту память.

Теперь рассмотрим, что происходит, когда мы пытаемся сделать:

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

В момент, когда мы вызываем функцию "хранится" в buffer, объект X уже был уничтожен, а память, удерживающая его, была [потенциально] освобождена. Независимо от того, были ли X TriviallyCopyable, мы больше не имеем X. У нас есть художник, ранее известный как X.

Поскольку для управления своими собственными объектами std::function он не может быть TriviallyCopyable, даже если мы добавили требование, чтобы все управляемые файлы были TriviallyCopyable.


Чтобы работать в realloc_vector, вам нужно либо что-то вроде function_ref (или std::function<>*) (то есть тип, который просто не владеет никакими ресурсами), либо вам нужно реализовать свою собственную версию из function, что (a) сохраняет свое собственное хранилище в качестве члена, чтобы избежать выделения памяти, и (b) является только конструктивным с TriviallyCopyable callables, так что он сам становится тривиально копируемым. Какое бы решение ни было лучше, зависит от того, что делает ваша программа.

Ответ 2

Но мне все еще очень любопытно, что является прямой причиной недействительности std:: function copy выше.

std::function не может быть TriviallyCopyable (или условно TriviallyCopyable), потому что в качестве универсальной обертки объекта-вызывающего объекта он не может предположить, что сохраненный вызываемый TriviallyCopyable.

Подумайте о том, как реализовать свою собственную версию std::function, которая поддерживает только TriviallyCopyable вызываемые объекты (используя фиксированный буфер для хранения) или использовать вектор указателей функций, если это применимо в вашей ситуации.

Ответ 3

Чтобы быть тривиально скопируемым, это то, что по своей сути связано с данным типом, а не с объектом.
Рассмотрим следующий пример:

#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
}

Как вы могли бы применить свойство после того, как вы назначили функцию лямбда, которая не может быть тривиально скопирована?

То, что вы ищете, будет машиной времени исполнения, которая принимает решение на основе фактического объекта, назначенного функции.
Это не работает std::is_trivially_copyable.
Поэтому компилятор должен принять решение во время компиляции по данной специализации для std::function. Для него общий контейнер для вызываемых объектов, и вы можете назначить его тривиально скопируемым объектам, а также объекты, которые не являются тривиально скопируемыми, остальное само собой разумеется.

Ответ 4

Функция std:: может выделять память для захваченных переменных. Как и для любого другого класса, который выделяет память, он не может быть тривиально скопирован.