Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a copy safe container with std::list iterators stored in a std vector in c++?

For my GUI i need a class with the following purposes to manage controls (windows, buttons etc)

  • random access to the elements by [index]
  • random access to the elements by ["key"]
  • pointer stability, so ptr=&container[index] wont change if elements are added or erased
  • copy safety. All elements must by stored in the container and copied if '=' is used like container2=conatiner1 (deep-copy)
  • the order of the elements in the list must be changeable, yet the pointers to the elements must remain valid. If ptr1=container[1] and ptr2=container[2], then after swapping the order of 1 and 2, ptr1==container[2] and ptr2==container[1]

I came to the conclusion that std::list provides the stability for the pointers that I need and std::vector the random access. So I have the idea to store a tuple of std string and iterator in a vector. However, the iterators are all invalid after the container is copied.

Any suggestions on how to tackle this problem best?

Here the main code of the current approach (only important parts are included):

template < class T >
class ControlList 
{
    struct Tuple{std::string first;typename std::list<T>::iterator second;};
    std::vector<Tuple> list;
    std::list<T> objects;

    inline T& operator [](int i)
    {
        return *list[i].second;
    }
    inline T& operator [](std::string s)
    {
        loopi(0,vlist.size()) 
        if(s==vlist[i].first) 
            return *vlist[i].second;
    }
}

The string access is slow, but usually the container has not more than 10 elements and its used rarely in the program.

Update:

The shared pointer is already good, but cannot solve for the deep copy I need. Lets say I have window2=window1. Now if I have a shared pointer, then pushing a button in window2 also pushes the same button in window1, which is not wanted. I really need a new instance of all objects contained in the container.

Is it possible to override the copy constructor to create new instances of the objects referenced by the smart pointers ?

Both, windows and buttons are stored in a ControlList , where a window contains multiple lists.

Update2:

Overriding the copy constructor and the assignment constructor has apparently solved the problem

Update3:

I just released the GUI this class was intended for under MIT.

Download here.

like image 505
Marco Polo Avatar asked Oct 20 '22 07:10

Marco Polo


1 Answers

If you were to use std::vector<std::pair<std::string, std::unique_ptr<T>>>, you could copy the items however you wanted to, and the resulting value would just require one more step of indirection to access. This would eliminate much of the complexity you have right now with 3 different structures. As a bonus, the items would also automatically cleanup after itself.

If you require owner-observer semantics with the pointers, you could instead opt for std::shared_ptr<T> and std::weak_ptr<T>. Shared pointers can easily create weak pointers, which act as non-owning observers which do not affect the referencing counting of the shared pointer.

Edit: Just to add on, shared_ptr and the other smart pointers are C++11 and later-exlcusive. If you desire C++03-compatible solutions, you can look to past Boost implementations or perhaps create one yourself by observing the C++11/14 spec.

Edit2: Here is some code to assist:

http://coliru.stacked-crooked.com/a/a9bf52e5428a48af

#include <vector>  //vector
#include <memory>  //smart pointers
#include <utility> //pair
#include <string>  //string
#include <iostream>//cout

template <class T>
class Container {
public:
    inline void push(const std::string& s, const T& t) {
        objects.push_back(std::pair<std::string, std::shared_ptr<T>>(s, std::make_shared<T>(t)));   
    }

    inline T& operator [](const size_t& i)
    {
        return *(objects[i]->second);
    }
    inline T& operator [](const std::string& s)
    {
        for (auto it : objects) {
            if(s == it.first) { 
                return *(it.second);
            }
        }

        //welp, what do you do here if you can't find it?
    }
private:
    std::vector<std::pair<std::string, std::shared_ptr<T>>> objects;
};

int main() {
    Container<int> cont;
    std::string str {"hi"};
    int i {2};

    cont.push(str, i);

    //This is good...
    std::cout << cont["hi"] << std::endl;

    //But undefined behavior!
    std::cout << cont["02"] << std::endl;
    return 0;
}
like image 199
CinchBlue Avatar answered Nov 01 '22 11:11

CinchBlue