Manejo de memoria en C++

Samsung Research Tijuana

Tipos de memoria en C++

Memoria estática (Stack)

Los objetos en memoria estática son destruidos automáticamente al finalizar el bloque donde fueron declarados.

Memoria dinámica (Heap)

Los objetos en memoria dinámica continúan con vida hasta que sean destruidos explícitamente, esto permite que salgan del bloque en el que fueron declarados.

Ejemplo: memoria estática

#include <iostream>

int c = 1;    /* Global */

int main()
{
    int a = 1;    /* Bloque */
    int b = 1;

    {
        int a = 1;    /* Segunda declaración de 'a' oculta la primera declaración. */

        std::cout << a++ << std::endl; /* 1 */

        b += a;
        c = 3;
    }

    ++a;
    ++c;

    std::cout << a << std::endl;        /* 2 */
    std::cout << b << std::endl;        /* 3 */
    std::cout << c << std::endl;        /* 4 */
}

Apuntadores

Declaración 

La sintaxis para declarar una variable apuntador es:

    T *nombre= valor;

Donde:

T: El tipo del dato al que se apunta.

nombre: El nombre de la variable apuntador.

valor: El valor inicial del apuntador (opcional).

int *intPointer = nullptr;

Apuntadores

Operadores

Operador * (Indirección)

Regresa una referencia al contenido del apuntador.

*intPointer = 5;
int number = *intPointer;

Operador -> (Acceso a miembro)

Acceso al miembro de una clase o estructura contenida por un apuntador. Exactamente igual a utilizar "(*T).<miembro de T>"

std::string *name = getName();
int nameLenght = name->size();

Apuntadores

Operadores

Operador & (Dirección de)

Regresa la dirección de memoria del objeto al cual se le aplique el operador en forma de un apuntador. 

std::string name = "Victor";
std::string *namePtr = &name;

Apuntadores

Operadores

Operador new

Este operador crea un nuevo objeto en memoria dinámica y regresa un apuntador al objeto creado. 

double *doublePointer = new double(10.0);

Operador delete

Destruye un objeto en memoria dinámica y libera el espacio de memoria que el objeto ocupaba.

delete doublePointer;

Ejemplos

int main()
{
    int i = 5;                 /* Objeto en memoria estática. */
    int *ptr1;                 /* Apuntador no inicializado. */
    int *ptr2 = nullptr;       /* Apuntador nulo. */
    int *ptr3 = &i;            /* Apuntador a la dirección de i. */
    int *ptr4 = new int(10);   /* Apuntador a un espacio de memoria dinámica. */

    std::string *name = getName();

    *ptr1 = name->size();      /* Error: Acceso de memoria invalido */
    *ptr2 = name->size();      /* Error: ptr1 es nulo */
    *ptr3 = name->size();      /* Ok, modifica el valor de i */
    *ptr4 = name->size();      /* Ok, modifica el valor del objeto en memoria dinámica. */

    delete ptr4;               /* Objetos en memoria dinámica se borran explícitamente. */
    delete name;
    
    return 0;
}

Ejemplo: memoria dinámica 

#include <string>

class Cellphone
{
public:
    Cellphone(std::string brand)
        : mBrand(brand)
    {}

    std::string brand() const
    {
        return mBrand;
    }

private:
    std::string mBrand;
};
#include <iostream>
#include "Cellphone.h"

Cellphone* badPhoneFactory(std::string brand)
{
    Cellphone p(brand);
    return &p;
}

Cellphone* phoneFactory(std::string brand)
{
    Cellphone *p = new Cellphone(brand);
    return p;
}

int main()
{
    Cellphone *samsung = phoneFactory("Samsung");
    Cellphone *apple = badPhoneFactory("Apple");

    std::cout << samsung->brand() << std::endl;
    std::cout << apple->brand() << std::endl; /* Error */

    delete samsung;
    delete apple;
}

Cellphone.h

main.cpp

RAII

RAII (Resource Acquisition Is Initialization)

Un objeto debe reservar sus recursos durante la inicialización (Constructor) y liberarlos durante su destrucción (Destructor).

class XmlParser
{
public:
    XmlParser(const std::string &xmlFilePath)
        : xmlFile(new File(xmlFilePath)),
          contents(new ByteArray())
    {
        if (xmlFile->open(File::ReadOnly))
        {
            contents = xmlFile->readAllBytes();
        }
    }

    ~XmlParser()
    {
        if (xmlFile != nullptr && xmlFile->isOpen()) 
        {
            xmlFile->close();
        }
        delete xmlFile;
        delete contents;
    }

    XmlObject parse() { /* Hacer algo con el contenido. */ }

private:
    File *xmlFile;
    ByteArray *contents;
}

RAII

class XmlParser
{
public:
    XmlParser(const std::string &xmlFilePath)
        : xmlFile(new File(xmlFilePath)),
          contents(new ByteArray())
    {
        if (xmlFile->open(File::ReadOnly))
        {
            contents = xmlFile->readAllBytes();
        }
    }

    ~XmlParser()
    {
        if (xmlFile->isOpen()) 
        {
            xmlFile->close();
        }
        delete xmlFile;
        delete contents;
    }

    XmlObject parse() { /* Hacer algo con el contenido. Puede causar excepción. */ }

private:
    File *xmlFile;
    ByteArray *contents;
}

Contenedor de apuntador

template <typename T>
class PointerWrapper
{
public:
    PointerWrapper(T *pointer)
        : pointer(pointer)
    {}

    ~PointerWrapper()
    {
        delete pointer;
    }

    T& operator*() 
    { 
        return *pointer;
    }

    T* operator->() { /* Implementacion */ }
    T& operator=() { /* Implementacion */ }

private:
    T *pointer;
}

Uso del contenedor

class XmlParser
{
public:
    XmlParser(const std::string &xmlFilePath)
        : xmlFile(new File(xmlFilePath)),
          contents(new ByteArray())
    {
        if (xmlFile->open(File::ReadOnly))
        {
            contents = xmlFile->readAllBytes();
        }
    }

    ~XmlParser()
    {
        if (xmlFile->isOpen()) 
        {
            xmlFile->close();
        }
    }

    XmlObject parse() { /* Hacer algo con el contenido. Puede causar excepción. */ }

private:
    PointerWrapper<File> xmlFile;
    PointerWrapper<ByteArray> contents;
}

STL Smart Pointers

Un smart pointer es un contenedor para un apuntador regular que se encarga de eliminar automáticamente el objeto dinámico y liberar la memoria ocupada por el apuntador. Este tipo de apuntadores fueron introducidos en C++11.

std::unique_ptr

Es un apuntador que tiene el control del objeto al que apunta, cuando se destruye la instancia de std::unique_ptr también se destruye el objeto al que apunta. Por lo tanto solo puede haber una sola instancia apuntando al mismo objeto.

void parseXml(const std::string &xmlFilePath)
{
    XmlParser *parser = getParser();
    
    try 
    {
        parser->parse();
        delete parser;
    }
    catch (Exception &e)
    {
        std::cout << e.what();
        delete parser;
    }
}
void parseXml(const std::string &xmlFilePath)
{
    std::unique_ptr<XmlParser> parser(getParser());
    
    try 
    {
        parser->parse();
    }
    catch (Exception &e)
    {
        std::cout << e.what();
    }
}

std::shared_ptr

Este apuntador mantiene un conteo de instancias, el objeto en memoria dinámica se destruye cuando el conteo de instancias llega a cero. Por lo tanto es posible tener muchas instancias de std::shared_ptr apuntando al mismo espacio de memoria.

Ejemplo: std::shared_ptr 

#include <string>

class Cellphone
{
public:
    Cellphone(std::string brand)
        : mBrand(brand)
    {}

    std::string brand() const
    {
        return mBrand;
    }

private:
    std::string mBrand;
};
#include <iostream>
#include <memory>
#include "Cellphone.h"

std::shared_ptr<Cellphone> phoneFactory(std::string brand)
{
    std::shared_ptr<Cellphone> p = 
        make_shared<Cellphone>(brand);
    return p;
}

int main()
{
    auto samsung = phoneFactory("Samsung");
    std::cout << samsung->brand() << std::endl;
}

Cellphone.h

main.cpp

Made with Slides.com