Kurzusok
/
C++
C++
/
Tanterv
/
Okos mutatók
Ok

Okos mutatók ebben a kurzusban: C++

1 feladat

A(z) Okos mutatók fogalomról

Az okos mutatók a modern C++ egyik olyan eszköze, amely automatikus memóriakezelést biztosít, és segít megelőzni a nyers mutatókhoz gyakran társított memóriaszivárgásokat és lógó mutatókat. A nyers mutatók köré épülő burkolóként működnek, és olyan további funkciókkal egészítik ki őket, mint például a memória automatikus felszabadítása, amikor a mutatóra már nincs szükség.

Általános szintaxis

Az okos mutatók általában osztálysablonként valósulnak meg a C++ szabványos könyvtárában. A két leggyakrabban használt okos mutató a std::unique_ptr és a std::shared_ptr.

Egyedi mutatók

A std::unique_ptr olyan okos mutató, amely kizárólagosan birtokolja az objektumot. Biztosítja, hogy egy adott pillanatban csak egy std::unique_ptr objektum birtokolja az erőforrást. Amikor a birtokló std::unique_ptr megsemmisül vagy alaphelyzetbe állítják, automatikusan megsemmisíti az objektumokat, és felszabadítja a memóriájukat.

#include <memory>
// Declaring and defining a unique pointer
auto rightful_king_of_england = std::make_unique<std::string>("Excalibur");

// Unique pointers cannot be copied or assigned
auto mordred = rightful_king_of_england; // Error: Cannot copy a unique_ptr

A std::make_unique() előnyei

Amikor std::unique_ptr-t hozol létre, érdemesebb a std::make_unique()-ot használni ahelyett, hogy közvetlenül a new operátorral foglalnál memóriát. A std::make_unique() több előnnyel is jár:

  1. Kivételbiztonság: A std::make_unique() garantálja a kivételbiztonságot. Ha az objektum létrehozása közben kivétel keletkezik, a memória automatikusan felszabadul, így nem alakul ki memóriaszivárgás.
  2. Átláthatóság: A std::make_unique() használatával a kód világosabb és tömörebb lesz. Nem kell külön megadnod a lefoglalandó típust, mert a sablonargumentumok automatikusan levezetődnek.
  3. Optimalizálási lehetőségek: A fordító hatékonyabban tudja optimalizálni a std::make_unique() hívást, mint a new operátorral kézzel végzett memóriafoglalást, ami jobb teljesítményt eredményezhet.
  4. A helytelen használat elkerülése: Ha a std::unique_ptr-t kézzel hozod létre, lehetőség van a mögöttes erőforrás törlésére. Ez nem definiált viselkedéshez vezetne, amikor a std::unique_ptr a scope végén megpróbálja törölni.

Megosztott mutatók

A std::shared_ptr olyan okos mutató, amely lehetővé teszi, hogy több std::shared_ptr objektum megossza ugyanazon erőforrás tulajdonjogát. Nyilvántartja, hogy hány megosztott mutató hivatkozik az erőforrásra, és csak akkor szabadítja fel a memóriát, amikor az erőforrást birtokló utolsó megosztott mutató kikerül a scope-ból vagy alaphelyzetbe állítják.

// Declaring and defining a shared pointer to a dynamically allocated string
auto martian_congressional_republic = std::make_shared<std::string>("protomolecule");

// Creating more shared pointer that shares ownership
auto outer_planets_alliance = martian_congressional_republic;
auto united_nations = martian_congressional_republic;
Caution

A C++17-es és korábbi verziókban a std::shared_ptr tömbökkel való használata std::make_shared<T[]> segítségével nem támogatott közvetlenül. Bár lehet tömböket lefoglalni a std::make_shared<T[]>-jal, ha közvetlenül ezekből hozol létre megosztott mutatókat, az nem definiált viselkedéshez vezethet, mert az egyedi objektumok és a tömbök memóriakezelése eltér egymástól. Ehelyett érdemes std::vector-t vagy egyéni törlőfüggvényeket használni, hogy hatékonyan kezeld a tömböket megosztott mutatókkal. Amikor C++17-ben tömböket foglalsz és megosztott mutatókat használsz, mindig győződj meg arról, hogy a megoldásod kompatibilis a fordítóddal és a szabványos könyvtár implementációjával.

A std::make_shared() előnyei

A std::make_unique()-hoz hasonlóan a std::make_shared() is olyan előnyöket kínál, mint a jobb memóriahatékonyság, a kivételbiztonság és az olvashatóság. A vezérlőblokk és a kezelt objektum memóriafoglalását egyetlen műveletbe vonja össze, ami növeli a hatékonyságot és csökkenti a memóriaszivárgás kockázatát. Emellett a sablonargumentumok automatikus levezetése egyszerűbbé teszi a kódot és javítja az olvashatóságát. A std::make_shared() használata tisztább, biztonságosabb és hatékonyabb kódot eredményez, amikor C++-ban std::shared_ptr objektumokkal dolgozol.

Gyenge mutatók

A std::weak_ptr a std::shared_ptr társosztálya, amely nem tulajdonló, „gyenge” hivatkozást biztosít egy megosztott mutató által kezelt objektumra. Lehetővé teszi az erőforrás elérését anélkül, hogy befolyásolná annak élettartamát. A gyenge mutatók olyan helyzetekben hasznosak, amikor körkörös hivatkozásokat kell megszakítani a memóriaszivárgások elkerülése érdekében.

// Creating a shared pointer
auto your_account = std::make_shared<std::string>("secret_subscription_password");
// Creating a shared pointer that shares ownership
auto your_flatmates_account = your_account;

// Creating a weak pointer from the shared pointer
auto your_flatmates_boyfriends_account = your_flatmates_account;
// if your_account and your_flatmates_account are deleted, there is no more reference to the shared pointer.
// your_flatmates_boyfriends_account will be a null pointer and cannot use the associated object any longer.

A std::weak_ptr és a körkörös tulajdonlás

A std::weak_ptr-t kifejezetten a körkörös tulajdonlás, más néven körkörös hivatkozások problémájának megoldására tervezték, amely std::shared_ptr használatakor fordulhat elő.

Egy körkörös tulajdonlási helyzetben két vagy több std::shared_ptr objektum hivatkozik egymásra, és olyan kört alkotnak, amelyben egyik objektum sem törölhető, mert erős hivatkozást tartanak egymásra, ami memóriaszivárgáshoz vezet.

A std::weak_ptr megoldást kínál erre a problémára: lehetővé teszi gyenge hivatkozások létrehozását megosztott objektumokra anélkül, hogy azok referenciaszámához hozzájárulna. Ez azt jelenti, hogy megfigyelheti és elérheti a megosztott objektumot, de nem akadályozza meg annak törlését. Ha a megosztott objektumra mutató összes erős hivatkozás megszűnik, az objektum megsemmisül, és az őt megfigyelő gyenge mutatók automatikusan nullptr-re állnak vissza.

Íme egy rövid példa, amely bemutatja a std::weak_ptr használatát a körkörös tulajdonlás megszakítására:

struct Node {
    Node() { std::cout << "Node constructed\n"; }
    ~Node() { std::cout << "Node destructed\n"; }

    std::weak_ptr<Node> next;
};

// Creating two nodes
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();

// Creating a cycle by making each node point to the other
node1->next = node2;
node2->next = node1;

// Release strong references to nodes
node1.reset();
node2.reset();

// Nodes are automatically destructed due to weak references

A „Node constructed” kétszer jelenik meg, utána pedig kétszer a „Node destructed”.

Amikor a next nem gyenge mutató, hanem std::shared_ptr, körkörös tulajdonlási helyzet alakul ki, ami memóriaszivárgáshoz vezet, mert mindkét csomópont erős hivatkozást tart a másikra, és ez megakadályozza a megsemmisülésüket.

struct Node {
    Node() { std::cout << "Node constructed\n"; }
    ~Node() { std::cout << "Node destructed\n"; }

    std::shared_ptr<Node> next; // Change to shared_ptr
};

Ebben a helyzetben a node1 és a node2 is erős hivatkozást tart egymásra a std::shared_ptr-en keresztül, így kört alkotnak. Amikor a program kilép, és a reset() hívással megpróbálja feloldani az erős hivatkozásokat, mindkét csomópont referenciaszáma nullától különböző marad, mert mindegyik erős hivatkozást tart a másikra. Ezért a csomópontok nem semmisülnek meg, ami memóriaszivárgáshoz vezet. A „Node constructed” kétszer jelenik meg, de utána egyetlen „Node destructed” sor sem következik.

Lógó mutatók és a std::weak_ptr

A lógó mutatók akkor fordulnak elő, amikor egy mutató olyan objektumra hivatkozik, amelyet már töröltek, és a mutató dereferálásakor nem definiált viselkedést okoznak. A std::weak_ptr segít elkerülni a lógó mutatókat azáltal, hogy nem tulajdonló, gyenge hivatkozást biztosít egy std::shared_ptr által kezelt objektumra. Így kerülheted el a lógó mutatókat a std::weak_ptr segítségével:

  1. Hozz létre egy std::weak_ptr-t: Ahelyett, hogy közvetlenül std::shared_ptr-t tartanál, hozz létre egy std::weak_ptr-t a megosztott objektumra.

  2. Ellenőrizd az érvényességét: Mielőtt használod a std::weak_ptr-t, ellenőrizd az érvényességét az expired() függvénnyel. Ez a függvény true-t ad vissza, ha a kapcsolódó megosztott objektumot törölték.

  3. Zárold le a std::weak_ptr-t: A megosztott objektum biztonságos eléréséhez használd a lock() függvényt, amely egy ugyanarra az objektumra mutató std::shared_ptr-t ad vissza, ha az még érvényes. Ha az objektumot törölték, a lock() egy üres std::shared_ptr-t ad vissza.

Íme egy kódpélda, amely bemutatja, hogyan kerülheted el a lógó mutatókat a std::weak_ptr segítségével:

auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();

// Create weak pointers
node1->next = node2;
node2->next = node1;

// Check validity and lock
if (auto lockedNode2 = node1->next.lock()) {
    // Use lockedNode2 safely
} else {
    // Handle case where node2 has been deleted
}

Ebben a példában a node1->next és a node2->next std::weak_ptr objektumok. A megosztott objektum elérése előtt az expired() függvénnyel ellenőrizzük az érvényességét. Ezután a lock() segítségével férünk hozzá biztonságosan a megosztott objektumhoz. Ez a megközelítés biztosítja, hogy ne keletkezzenek lógó mutatók, amikor std::weak_ptr-rel dolgozol.

Használati tanácsok

Alapértelmezés szerint használj okos mutatókat: kizárólagos tulajdonláshoz std::unique_ptr-t, megosztott tulajdonláshoz std::shared_ptr-t. A nyers mutatókat csak nem tulajdonló hivatkozásokhoz, illetve régi kóddal való együttműködéshez használd. A legtöbb esetben a std::unique_ptr elegendő a kizárólagos tulajdonláshoz, mert könnyűsúlyú memóriakezelést kínál a referenciaszámlálás többletköltsége nélkül. A std::shared_ptr-t takarékosan használd, mert többletköltséget és bonyolultságot hoz, hacsak nincs szükség valódi megosztott tulajdonlásra. A std::weak_ptr kifejezetten a körkörös függőségek megszakítására vagy a megosztott objektumok megfigyelésére szolgál, de ritkán van rá szükség.

Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg

Tanuld meg a(z) Okos mutatók fogalmat