Co dokładnie std::function konstruktor jest przechowywany w kontenerze?

W tym kodach testowych:

struct A {
    int sn;
    A() = delete;
    A(int v) : sn(v) { cout << "A::init(" << sn << ')' << endl; }
    A(const A& a) : sn(a.sn+1) { cout << "A::copy(" << sn << ')' << endl; }
    A(A&& a) : sn(a.sn+1) { cout << "A::move(" << sn << ')' << endl; }
    ~A() { cout << "A::delete(" << sn << ')' << endl; }
};

void func(int a, A &b) {
    cout << "func2:" << a << ',' << b.sn << endl;
}

int main(int argc, char *argv[]) {
    std::vector<std::function<void(void)>> fv;
    A a(1);
    cout << "call bind()" << endl;
    fv.emplace_back(std::bind(func,1,a));
    fv.front()();
    cout << "end of local scope" << endl;
}

Oświadczam konstruktory ruchu i kopiowania class A, aby gromadzić numer sekwencji, dzięki czemu mogę śledzić go jako utworzoną instancję NTH. Wynik to:

A::init(1)
call bind()
A::copy(2)
A::move(3)
A::move(4)
A::delete(3)
A::delete(2)
func2:1,4
end of local scope
A::delete(1)
A::delete(4)

Najpierw utworzona jest instancja a(1) jest tworzona w main() lokalnym zakresie, a następnie jest kopiowany jako nowa instancja a(2) Podczas wywołania std::bind(), a następnie ponownie przesunął się w std::function dynamicznie przydzielony Pamięć (lub jego wewnętrzna pamięć?) Aby przytrzymać kopię instancji std::bind(), więc mamy 3rd instancje A w posiadaniu std::function instancja.

Do tej pory jest zrozumiałe, ale dlaczego jest kolejna konstrukcja ruchu? I dlaczego trzecie instancja jest zniszczona przed drugą instancją, która ma być wyczyszczona, gdy zwraca std::bind()?

Jeśli przepiszę funkcję main() jako:

int main(int argc, char *argv[]) {
    A a(1);
    cout << "call bind()" << endl;
    std::function<void(void)> f(std::bind(func,1,a));
    f();
    cout << "end of local scope" << endl;
}

Wtedy wynik byłby:

A::init(1)
call bind()
A::copy(2)
A::move(3)
A::delete(2)
func2:1,3
end of local scope
A::delete(3)
A::delete(1)

Nie ma drugiego budowy ruchu i wszystko wygląda rozsądnie.

Co dokładnie wydarzyło się podczas dzwonienia {X0}}, aby utworzyć instancję std::function w tym przypadku?

3
RichardLiu 27 październik 2020, 04:48

1 odpowiedź

Najlepsza odpowiedź

Najpierw utworzona jest instancja a(1) jest tworzona w main() lokalnym zakresie, a następnie jest kopiowany jako nowa instancja a(2) Podczas wywołania std::bind(), a następnie ponownie przesunął się w std::function dynamicznie przydzielony pamięć

Brakuje tego kroku: std::function Konstruktor ma wartość

template<typename F>
std::function<void(void)>::function(F f);

emplace_back przekazuje std::bind - opakowanie zawierające a(2) jako rvalue, tak, ale ten konstruktor akceptuje tylko wartość, a nie przez referencję rvalue, więc a(2) zostaje przeniesiony i staje się a(3), aby zainicjować parametr konstruktora. wtedy konstruktor przesuwa tę wartość do dynamicznego przechowywania.

int main() {
    A a(1); // A::init(1)
    // this is the temporary that gets materialized in main's scope for the call to emplace_back
    auto wrapper = std::bind(func, 1, a); // A::copy(2);
    // emplace_back receives (a reference to) the temporary as an rvalue, and so it passes std::function's constructor a reference to the same object, also as an rvalue
    // but std::function's constructor only takes values...
    std::function<void(void)> f(std::move(wrapper));
    // initializing the parameter of constructor (in main/emplace_back's context): A::move(3)
    // constructor then initializes dynamically allocated object: A::move(4)
    // constructor destroys parameter object: A::delete(3)
    // f gets destroyed, takes dynamically allocated object with it: A::delete(4)
    // wrapper (the temporary) gets destroyed (originally at the ; after emplace_back): A::delete(2)
    // a gets destroyed: A::delete(1);
}
// Note: the reason A::delete(4) comes before A::delete(1) in my version but not yours is that your A(4) lives inside fv, which is created at the top of main and thus is the last thing destroyed, but mine lives inside f, which is created at the end and is the first thing destroyed

Różnica w uproszczeniu jest to, że nie będziesz realizowany wynik std::bind jako tymczasowy. Zasadniczo emplace_back wymaga, aby argumenty były już w pełni skonstruowane (podobnie jak każda rozmowa funkcji). Więc musisz wykonać std::bind (i wnętrze do niej) tylko po to, by zadzwonić emplace_back, i , a następnie emplace_back przesuwa ten obiekt do parametru konstruktora. Ale, gdy przekazujesz std::bind PRVALUE bezpośrednio do konstruktora std::function, obiekt parametrów konstruktora jest bezpośrednio skonstruowany przez obiekt wyników std::bind do obiektu parametru podczas jego wykonania.

Innym sposobem na powiedzenie: std::function Konstruktor musi być wywołany nowo utworzonym obiektem. Podczas korzystania z emplace_back funkcja interweniująca "zapomniała" std::bind - Call był nowo skonstruowanym obiektem, więc nowy obiekt jest skonstruowany przez wykonanie "obcych" ruchu. Po usunięciu emplace_back, a następnie fakt, że obiekt std::bind jest niedawno skonstruowany nie jest zapomniany, a ruch jest elegancki.

Inny inny sposób na powiedzenie: "Perfect" spedycja, jak twierdzi emplace_back jest faktycznie nie doskonały; Taka rzecz nie jest możliwa (kod użytkownika musi zostać zmodyfikowany). "Perfect" Przekazywanie do przodu Lvalues jako Lvalues, ale zwalcza zarówno XVALUES, jak i krwawienia jako ks. Wywołanie std::function Konstruktor z XValue powoduje dwa ruchy, ale dzwoniąc z prvalue powoduje jeden. Twoja druga wersja przekazuje prvalue do konstruktora, ale oryginał przechodzi XValue, więc mają inne zachowanie.

Możesz uzyskać pożądany wynik z funkcją opakowania:

template<typename F>
struct initializer {
    F f;
    operator decltype(f())() && {
        return std::move(f)();
    }
};
int main(int argc, char *argv[]) {
    std::vector<std::function<void(void)>> fv;
    A a(1);
    cout << "call bind()" << endl;
    fv.emplace_back(
        initializer{[&]() -> std::function<void(void)> {
            return std::bind(func, 1, a);
        }});
    fv.front()();
    cout << "end of local scope" << endl;
}

Dotyczy to połączenia do std::bind bezpośrednio pod połączeniem do {x1}} "s konstruktor (który znajduje się w return), więc prvalue bezpośrednio inicjuje parametr konstruktora bez przemieszczania się. std::function jest również skonstruowany bezpośrednio w "pustej" przestrzeni w wektorze, bez dalszego poruszania się (właśnie to jest emplace_back.

1
HTNW 27 październik 2020, 03:34