WebHU - Programozási kérdések és válaszok

A mozgatási szemantika használata a másolás elkerülésére, amikor a_visszaküldést egy egyéni tárolóba helyezi, nem kerüli el a másolást

Megvalósítottam egy egyéni tárolót (ugyanúgy, mint az std::vector), és megpróbálom úgy elkészíteni, hogy a 'push_back' függvénye kihasználja a mozgatási szemantikát, nehogy másolatot készítsen arról, ami vissza van tolva – különösen akkor, ha a a konténerbe tolandó tárgyat egy külső funkció visszaadja.

Miután sokat olvastam az áthelyezési szemantikáról és az egyéni tárolókról, még mindig nem találom, hogy a megközelítésem miért generál még mindig másolatot ahelyett, hogy az átadott objektumot a tároló belső dinamikus tömbjébe helyezné át.

Íme a konténerem egyszerűsített változata, így néz ki:

template<class T>
class Constructor
{
private:
    size_t size = 0;
    size_t cap = 0;
    T *data = nullptr;

public:
Constructor()
{
    cap = 1;
    size = 0;
    data = static_cast<T*>(malloc(cap * sizeof(T)));
}

~Constructor()
{ delete[] data; }

template<typename U>
void push_back(U &&value)
{
    if (size + 1 >= cap)
    {
        size_t new_cap = (cap * 2);
        T* new_data = static_cast<T*>(malloc(new_cap * sizeof(T)));
        memmove(new_data, data, (size) * sizeof(T));
        for (size_t i = 0; i<cap; i++)
        {
            data[i].~T();
        }

        delete[] data;

        cap = new_cap;
        data = new_data;
        new(data + size) T(std::forward<U>(value));
    }
    else
    {
        new(data + size) T(std::forward<U>(value));
    }

    ++size;
}

const T& operator[](const size_t index) const //access [] overloading
{
    return data[index];
}
};

Itt van egy egyéni osztály, amely üzeneteket nyomtat a példányai létrehozásakor, másolásakor vagy áthelyezésekor, hogy segítse a hibakeresést:

class MyClass
{
size_t id;

public:
MyClass(const size_t new_id)
{
    id = new_id;
    std::cout << "new instance with id " << id << std::endl;
}
MyClass(const MyClass &passedEntity)
{
    id = passedEntity.id;
    std::cout << "copied instance" << std::endl;
}
MyClass(MyClass &&passedEntity)
{
    id = passedEntity.id;
    std::cout << "moved instance" << std::endl;
}

void printID() const
{
    std::cout << "this instance's id is " << id << std::endl;
}
};

És itt van a külső funkció:

MyClass &foo(MyClass &passed)
{
    return passed;
}

Végül itt van a main függvény, amely egy tesztesetet futtat a fenti függvény és osztályok használatával a probléma megjelenítéséhez:

int main()
{
MyClass a(33);
std::cout << std::endl;

std::cout << "Using my custom container: " << std::endl;
Constructor<MyClass> myContainer;
myContainer.push_back(foo(a));
myContainer[0].printID();
std::cout << std::endl;

std::cout << "Using dinamic array: " << std::endl;
MyClass *dinArray = static_cast<MyClass*>(malloc(1 * sizeof(MyClass)));
dinArray = new(dinArray + 1) MyClass(std::forward<MyClass>(foo(a)));
dinArray[0].printID();
std::cout << std::endl;


system("Pause");
return 0;
}

A kimenet a következő:

new instance with id 33

Using my custom container:
copied instance
this instance's id is 33

Using dinamic array:
moved instance
this instance's id is 33

Amint látható, ha a MyClass példányát közvetlenül egy dinamikus tömbbe helyezzük, akkor csak a mozgatás konstruktor kerül meghívásra, a másolás konstruktor nem. Ha azonban visszatolom_a yClass példányt a Container példányába, a másoláskonstruktor továbbra is meghívásra kerül.

Valaki segítene megérteni, hogy pontosan mit csinálok rosszul? Hogyan tudnám elérni, hogy az elemek másolat létrehozása nélkül kerüljenek a tárolóba?


  • Ez az indoklás a c++11 emplace_back() mögött. 05.12.2017
  • Ha emulálni szeretné a mozgatási szemantikát a C++03-ban, használja az std::swap: void emplace(vector<T>& v, T& value) { v.resize(v.size()+ 1); std::swap(v.back(), value); } parancsot. Előfordulhat, hogy bizonyos típusaihoz az std::swap értéket is meg kell adnia. Valamilyen pszeudomozgás-konstruktor. 05.12.2017

Válaszok:


1

Amikor hívja ezt a vonalat

myContainer.push_back(foo(a));

Az L-érték átkerül a push_back metódusba, és most olvassa el az std::forward - http://www.cplusplus.com/reference/utility/forward/,

Rvalue hivatkozást ad vissza az arg-hez, ha az arg nem egy lvvalue hivatkozás.

Ha az arg egy lvalue-referencia, a függvény az arg-t adja vissza anélkül, hogy módosítaná a típusát.

és a push_back-ban hívod

new(data + size) T(std::forward<U>(value));

de az value L-értékként lett átadva, és csak a MyClass(const MyClass &passedEntity) konstruktor hívható meg.

Ha a objektumot szeretne áthelyezni, írhat

myContainer.push_back(std::move(a)); // cast to R-reference

SZERKESZTÉS

A push_back függvényben ne használja a move-ot, az alábbiakban egy egyszerű példa látható. Tegyük fel, hogy ilyen osztályod van:

 struct Foo {
 int i;
 Foo (int i = 0) : i(i) { 
 }
 ~Foo () { 
 }
 Foo (const Foo& ) { 
 }
 Foo& operator=(const Foo&) { 
    return *this;
 }
 Foo (Foo&& f) 
 { 
    i = f.i; 
    f.i = 0;   // this is important
 }
 Foo& operator=(Foo&& f) 
 {   
    i = f.i; 
    f.i = 0; // this is important
    return *this;
 }
};

2 funkciónk is van

template<class T> 
void process1 (const T& ) {
    cout << "process1" << endl;
}

template<class T>
void process (T&& obj) {
    cout << "process2" << endl;
    T newObj = forward<T>(obj);
}

és a bars függvények a push_back metódus megfelelői.

template <typename T>
void bar1 (T&& value) {
    process (move(value));   // you use move in your push_back method 
}

template <typename T>
void bar2 (T&& value) {
    process (forward<T>(value));
}

most 4 esetet kell figyelembe vennünk:

[1] pass L-érték, változat előre

Foo f(20);
bar2 (f);
cout << (f.i) << endl; // 20

[2] Pass R-érték, verzió előre

Foo f(20);
bar2 (move(f));
cout << (f.i) << endl; // 0, it is OK bacuse we wanted to move 'f' object

[3] Pass R-érték, áthelyezéssel rendelkező változat

Foo f(20);
bar1 (move(f));
cout << (f.i) << endl; // 0, ok, we wanted to move 'f' object

[4] átadja az L-értéket, a push_back metódusban mozgatást tartalmazó verzió

Foo f(20);
bar1 (f);
cout << (f.i) << endl; // 0 !!! is it OK ? WRONG

utolsó esetben a f értéket adtuk át L-értéknek, de ez az objektum a bar1 függvényben került áthelyezésre, számomra ez furcsa viselkedés és helytelen.

05.12.2017
  • Köszönöm a választ. Tehát a push_back függvényemen belül miért nem tudtam egyszerűen megtenni a new(data + size) T(std::move(value));-t ahelyett, hogy a std::move-t csak a push_back hívásakor használnám? 05.12.2017
  • A move nem használható a push_back metóduson belül, mert a move mindig R-referenciát ad vissza. A forward visszatérési típusa a value típusától függ. Ha a push_back-t L-értékkel hívod (a a objektumot .push_back(a) használatával adod át), akkor az L-referenciát value típusként kapod, és ez rendben van, mert nem akarod áthelyezni a value objektumot. a push_back módszert. Ha a push_back-t R-értékkel hívja (az 'a'-t a .push_back(move(a)) használatával adja meg), a value R-referenciatípussal rendelkezik, és a mozgatható konstruktor meghívható. 05.12.2017
  • Furcsa, pont ezen változtattam, azaz std::move() ahol korábban volt std::forward<U>(), és most tökéletesen működik. Egész nap használtam minden észrevehető probléma nélkül. 06.12.2017

  • 2

    Nem biztonságos a memmove végrehajtása objektumokkal C++ nyelven. További információt itt talál: A memcpy vagy a memmove problémákat okoz az osztályok másolásakor?

    Ha ez C++11-től kezdve, akkor az új elhelyezést és az áthelyezés konstruktort szeretné használni. (Valószínűleg elhelyezheti az új elhelyezést, hacsak nem igazán akarja saját maga lefoglalni a memóriát)

    Ha ez a C++ bármely más verziója, akkor el kell fogadnia, hogy vagy át kell másolnia az objektumot (mint az stl többi részét), vagy az objektumnak egy olyan függvényt kell megvalósítania, mint a void moveTo(T& other)

    05.12.2017
    Új anyagok

    A rádiógomb ellenőrzött eseményének használata a jQueryben
    Ebben a cikkben látni fogjuk, hogyan kell dolgozni a jquery választógombbal ellenőrzött eseményeivel. A választógombok HTML gombok, amelyek segítenek kiválasztani egyetlen értéket egy csoportból...

    Körkörös függőségek megoldása terraformban adatforrásokkal – lépésről lépésre
    Mi az a körkörös függőségek Dolgozzunk egy egyszerű eseten, amikor az SQS-sor és az S3-vödör közötti körkörös függőség problémája van egy egymástól függő címkeérték miatt. provider..

    Miért érdemes elkezdeni a kódolást 2023-ban?
    01100011 01101111 01100100 01100101 — beep boop beep boop Világunk folyamatosan fejlődik a technológia körül, és naponta fejlesztenek új technológiákat a valós problémák megoldására. Amint..

    🎙 Random Noise #2  – Örökbefogadás és hit
    az analitika íratlan világának gondozása Szeretné, hogy ezek a frissítések a postaládájába kerüljenek? Iratkozzon fel itt . "Ha önvezető autókat gyártanak, akkor mi miért ne..

    A legrosszabb politika és prediktív modellek májátültetésre jelöltek számára az Egyesült Államokban
    A máj (vagy óangolul lifer) az emberi test legnehezebb belső szervére utal, amely csendesen működik a nap 24 órájában. Mit csinál a máj? 500 feladatot hajt végre a szervezet egészségének..

    5 webhely, amely 2022-ben fejleszti front-end fejlesztői készségeit
    Frontendmentor.io A tényleges projektek létrehozásával a Frontendmentor.io segítséget nyújt a front-end kódolási képességeinek fejlesztésében. A kódolást azután kezdheti meg, hogy..

    Mikor kell használni a Type-t az interfészhez képest a TypeScriptben?
    A TypeScript a JavaScript gépelt szuperkészlete, amely statikus gépelést ad a nyelvhez. Ez megkönnyíti a robusztus és karbantartható kód írását azáltal, hogy a hibákat a fordítási időben..