Max Beikirch
Max Beikirch

Reputation: 2143

Retrieve a non-const element from a const c++-vector

in my program I have global data.

Every program module must have read and write access to the data. As of now, I do not use threading, but Qt's signals and slots and therefore - although I did not yet encounter crashes - I think I'll need synchronization somewhen.

Therefore, every module holds the data like this:

const std::vector<T>& data;

where T is a custom class. Therefore, every module can read the data. To keep it consistent, the vector itself is const to prohibit concurrent deletions or removals. Those are done using global functions (like addToData(T elem), removeFromData(int id)) which can be synchronized. Note that the vector itself is declared to be shared by reference, such that changes in one of the global functions above will result in consistent data in every program module.

==> That means that data can be read and added/removed from everywhere in a safe way.

The problem I encountered is modification of the data. The setters of T are aware of the race-conditions. Using data, I want to allow calls like data.at(3).setAttr("hello"), but for constant vectors, at() just returns constant references. Why and how can I make it work? I can cast the constness away, but that feels wrong.

I am also open for suggestions considering my architecture.

Upvotes: 2

Views: 964

Answers (3)

Peter
Peter

Reputation: 5728

Since you asked for suggestions

  1. You can wrap vector, with a class that works mostly the same. This would also get rid of the global functions you mention, which is good. If you want the instance of your class to be const, use a mutable vector internally.
  2. const_cast, but try to hide it somewhere in a function.
  3. Store smart pointers. I hope I'm not wrong with this (it's been a while), but I think you can retrieve a non-const element through a const smart pointer.

To elaborate on (1) since I was asked to in the comment:

You need the functionality of vector, it's a pretty good match for your needs. But the interface of vector is clumsy for what you need. This is a situation which is often encountered, and a wrapper is the default solution. A wrapper is a new class, with an interface that matches your needs, but it's implementation is pretty much just delegating all the work to another class. The goal of using a wrapper is to make the wrapper object easier to use right and harder to use wrong. It might look somewhat like this (not tested, will not compile):

class AWrapperForDemonstration
{
public:
    MyCustomClass& GetByIndex(int i) const // can throw
    {
         std::lock_guard<std::mutex> lock(_mutex);
         return _storage[i];
    }
    size_t Size(int i) const
    {
         std::lock_guard<std::mutex> lock(_mutex);
         return _storage.size();
    }
    void Add(MyCustomClass& addThis)
    {
         std::lock_guard<std::mutex> lock(_mutex);
         _storage.push_back(addThis);
    }
    bool Remove(MyCustomClass& removeThis)
    {
         std::lock_guard<std::mutex> lock(_mutex);
         auto it =_storage.find(removeThis);
         if (it == _storage.end())
             return false;
         _storage.erase(it);
    }
    template <F> void ForEach(F const& f) const
    {
        std::lock_guard<std::mutex> lock(_mutex);
        for (auto& i : _storage)
            f(i);
    }
private:
    std::vector<MyCustomClass> _storage;
    std::mutex _mutex;
}

Upvotes: 2

bobah
bobah

Reputation: 18864

  1. Synchronizing writes leaving reads non-synchronized may/will corrupt memory too.
  2. Casting constancy away smells.

Working low overhead solution (though not well encapsulated) is below. Idea is well summarized in a @JonathanWakely's comment: "add a global function that takes a functor and applies it to the non-const vector"

header.h

struct no_synchronization {
    struct mutex {}
    struct guard {
      guard(mutex&) {}
    };
};

struct serialized {
   typedef std::mutex mutex;
   typedef std::lock_guard<mutex> guard;
};

template <class T, class S = no_synchronization>
class spaghetti {
    typedef typename S::mutex mutex;
    typedef typename S::guard guard;

    static mutex mutex_;
    static std::vector<T> vector_;

    template <class F>
    static void read_global_vec(F const& f) {
        guard lock(mutex_);
        std::vector<T> const& ref = vector_;
        f(ref);
    }

    template <class F>
    static void write_global_vec(F const& f) {
        guard lock(mutex_);
        f(vector_);
    }
}

If the contents of the vector is changing infrequently then you can use copy-on-write with a shared_ptr to keep contention to the minimum.

Upvotes: 2

Paul Evans
Paul Evans

Reputation: 27567

This scenario is exactly where you want to cast constness away. You have carefully designed your system to work correctly so don't feel badly about casting away const when you're completely ready for it's and it the right thing to do.

Upvotes: 2

Related Questions