Delete items from a list represented in a QTabWidget

3

I have a class MainWindow derived from QMainWindow

class MainWindow : public QMainWindow
{
    Q_OBJECT

private:

    struct MetaObra
    {
        InterfazObra* miobra;
        QString nombrefichero;
        MetaObra():miobra(nullptr){}
    };
    std::list<MetaObra>ListaObras;
    std::list<MetaObra>::iterator obraActual;
.........................
}

When I add an element of the type MetaObra to the list, a tab of a QTabWidget appears. When I change the active tab the iterator points to the MetaObra of the list .... up there everything is fine. The problem I have when closing tabs, if I close a tab that is not the last, the iterator goes to the next tab, but when I want to close that, the program fails and closes.

This would be the code to close the tabs:

void MainWindow::ActionCerrar()
{   
    std::list<MetaObra>::iterator obraBorrar = obraActual;    
    if (ListaObras.size()>1)
    {

        if (obraActual!=ListaObras.end() && std::next(obraActual)==ListaObras.end())//ultimo elemento
        {

            obraActual=std::prev(obraActual);
        }
        else
        {
            obraActual=std::next(obraActual);
        }
    }
    delete obraBorrar->miobra;
    ListaObras.erase(obraBorrar);
}

What I do first is check if there is more than one item in the list. If so, I check if it's the last one. If it is the last one, I put the iterator in the previous element. If it is not, I put it in the next element. Then proceed to delete the contents of the pointer and finally remove the item from the list. I have checked the behavior and it is correct.

Imagine that I have 3 works, one in each tab.Obra1, Obra2, Obra3. If I close the tab of the Obra2, Obra3 is set as an active tab. If I close it then, the program fails. However, if before closing the tab change to Obra1 as active tab and then return to Obra3, it closes normally. When I close the tabs in decreasing order there are no problems, but if I close them from "left to right"

    
asked by user3733164 21.04.2017 в 02:19
source

2 answers

3

It's simpler than what you're trying.

std::list::erase returns an iterator to the next item in the list. The idea is then to delete the element yes and yes and verify if the iterator returned is the end of the list. If that happens and it turns out that the list has elements, it is enough to back off the iterator one position to get the iterator to a valid tab:

void MainWindow::ActionCerrar()
{
  if( obraBorrar != ListaObras.end() ) // Es necesario este chequeo?
  {
    delete obraBorrar->miObra;
    obraBorrar = ListaObras.erase(obraBorrar);

    if ( obraBorrar == ListaObras.end() && !ListaObras.empty() )
      obraBorrar = std::prev(obraBorrar);
  }
}

Of course, consider implementing a destructor for the structure and thus avoid the delete :

struct MetaObra
{
    InterfazObra* miobra;
    QString nombrefichero;
    MetaObra():miobra(nullptr){}
    ~MetaObra(){ delete miobra; }
};

void MainWindow::ActionCerrar()
{
  if( obraBorrar != ListaObras.end() ) // Es necesario este chequeo?
  {
    // delete obraBorrar->miObra; ya no es necesario
    obraBorrar = ListaObras.erase(obraBorrar);

    if ( obraBorrar == ListaObras.end() && !ListaObras.empty() )
      obraBorrar = std::prev(obraBorrar);
  }
}

Edit: The above solution can give problems if the elements are inserted in the list by copy (a possible example):

MetaObra obra;
// ...
ListaObras.insert(ListaObras.end(),obra);

Since this design would be calling the constructor copy of MetaObra to copy the object in the container and then destroy the object obra , which would release the corresponding resources.

There are two broad solutions:

  • Use pointers in the list. This eliminates the copies of objects and the destruction of them, but forces us to use dynamic memory:

    std::list<MetaObra*> ListaObras;
    
    MetaObra* obra = new MetaObra();
    // ...
    ListaObras.insert(ListaObras.end(),obra); // Ya no hay copia de objetos sino de punteros
    
  • Use the move syntax :

    MetaObra obra;
    // ...
    ListaObras.insert(ListaObras.end(),std::move(obra));
    // A partir de este punto ya no se debe usar 'obra'
    

    If the above does not work first you may need to enable the move constructor . As it should not be necessary to give it a specific functionality, it could be left with its default implementation:

    struct MetaObra
    {
        MetaObra(MetaObra&&) = default; // Gracias a default no necesita implementacion
    
        // Operador de asignacion move
        MetaObra& operator=(MetaObra&&) = default;
    };
    

    The great advantage of this solution is that you avoid resorting to dynamic memory ... the big disadvantage is that you have to be careful not to make copies of the object that the pointer can throw ... maybe it would be convenient to disable the copy constructor and the assignment operator:

    struct MetaObra
    {
        MetaObra(MetaObra const&) = delete;
    
        // Operador de asignacion con copia
        MetaObra& operator=(MetaObra const&) = delete;
    };
    

    Thus the compiler will prevent you from creating copies of the objects. Think that for specific uses you can always resort to references:

    MetaObra& obra = *ListaObras.begin(); // Suponemos que la lista no esta vacia
    obra.nombreFichero = "test"; // Se actualiza el nombre del elemento de la lista
    

    For more information about syntax move you can consult the following link

  • Use smart pointers (similar to the first solution but without having to deal directly with dynamic memory):

    std::list<std::shared_ptr<MetaObra>> ListaObras;
    
    std::shared<MetaObra> obra = std::make_shared<MetaObra>();
    // ...
    ListaObras.insert(ListaObras.end(),obra);
    

    This solution allows you to create copies of smart pointers. Now the object will only be destroyed when no smart pointer is referencing it.

    The disadvantage of this solution is that if you are not very careful with the use of pointers you can create memory leaks.

answered by 21.04.2017 / 09:16
source
0

Well, this would be the adapted code of the function, according to @eferion's response:

void MainWindow::ActionCerrar()
{   
    if (!ListaObras.empty())
    {
        std::list<MetaObra>::iterator obraBorrar = obraActual;
        {
            obraActual = ListaObras.erase(obraActual);
            delete obraBorrar->miobra;
            if ( obraActual == ListaObras.end() && !ListaObras.empty())
            {
                obraActual = std::prev(obraActual);
            }
        }
    }
}
    
answered by 21.04.2017 в 10:05