1

Ok, the question title is a bit hard to phrase. What I am trying to achieve is create a template class with get/set functions that can handle simple types and structures.

This is simple for types such as integers and char, etc... But when the template type 'T' is a struct then it gets harder.

For example, here is a template class, where I have omitted various parts of it (such as constructor, etc), but it shows the get/set function:

EDIT: Only this class is allowed to modify the data, so passing a reference outside is not allowed. The reason is that I want to do a mutex around the set/get. I will/have update the functions...

template <class T> class storage
{
private:
   T m_var;
   pthread_mutex_t m_mutex;

public:
   void set(T value)
   {
      pthread_mutex_lock(&m_mutex);
      m_var = value;
      pthread_mutex_unlock(&m_mutex);
   }

   T get(void)
   {
      T tmp;
      // Note: Can't return the value within the mutex otherwise we could get into a deadlock. So
      // we have to first read the value into a temporary variable and then return that.
      pthread_mutex_lock(&m_mutex);
      tmp = m_var;
      pthread_mutex_unlock(&m_mutex);
      return tmp;
   }
};

Then consider the following code:

struct shape_t
{
   int numSides;
   int x;
   int y;
}

int main()
{
   storage<int> intStore;
   storage<shape_t> shapeStore;

   // To set int value I can do:
   intStore.set(2);

   // To set shape_t value I can do:
   shape_t tempShape;
   tempShape.numSides = 2;
   tempShape.x = 5;
   tempShape.y = 4;
   shapeStore.set(tempShape);

   // To modify 'x' (and keep y and numSides the same) I have to do:
   shape_t tempShape = shapeStore.get();
   tempShape.x = 5;
   shapeStore.set(tempShape);
}

What I want to be able to do, if its possible, is to set the members of shape_t individually via some means in the template class, something like:

shapeStore.set(T::numSides, 2);
shapeStore.set(T::x, 5);
shapeStore.set(T::y, 4);

And not have to use a temp var. Is this possible? how?

I looked at this answer, but it did not quite do what I wanted because it is for a specific structure type

Community
  • 1
  • 1
code_fodder
  • 15,263
  • 17
  • 90
  • 167

3 Answers3

4

Make your get() member return a reference:

T& get()
{
  return m_var;
}

Then you could say

shapeStore.get().x = 42;

Note it is good practice to add a const overload:

const T& get() const
{
  return m_var;
}

Also note that if your get and set methods really do nothing special, as in your example, you might consider making the data public and doing away with getters/setters:

template <class T> struct storage
{
   T m_var;
};

Edit: If you want to allow synchronised changes to the member, an option is to have a method that takes a modifying function. The function is applied inside the class, in your case, protected by the mutex. For example,

template <class T> struct storage
{
  storage() : m_var() {}
  void do_stuff(std::function<void(T&)> f)
  {
    std::lock_guard<std::mutex> lock(m_mutex);
    f(m_var);
  }
private:
   T m_var;
   std::mutex_t m_mutex;
};

Then you can modify members in a synchronised manner:

storage<shape_t> shapeStore;
shapeStore.do_stuff([](shape_t& s) 
                    { s.x = 42;
                      s.y = 100; });

If you don't have C++11 you can pass a function instead:

void foo(shape_t& s) { s.x = 42; }
shapeStore.do_stuff(foo);
juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • ah, yes I should have mentioned that. I don't want to edit the values outside of the class, the edit must be within the class - in my case because I wanted to do a mutex around the get/set. I will update the post, I made it too simple. But otherwise, nice simple answer :) – code_fodder Sep 25 '14 at 20:22
  • That is interesting... although, I can't quite see how I would apply that such that I can modify particular elements from the "outside" i.e. from main(), can you show an example of modifying just the "x" coodintate of shape_t? thanks very much! – code_fodder Sep 25 '14 at 20:47
  • Thanks juanchopanza, it's still not-quite what I am after, having to define a function to change the elements makes the code less readable/clear. But it is a valid option +1. – code_fodder Sep 29 '14 at 09:22
1

Your design is fairly workable for primitive types, but it requires you to replicate the entire interface of class types and quickly becomes unmanageable. Even in the case of primitive types, you might want to enable more complex atomic operations than simply get and set, e.g., increment or add or multiply. The key to simplifying the design is to realize that you don't actually want to interpose on every single operation the client code performs on the data object, you only need to interpose before and after the client code atomically performs a sequence of operations.

Anthony Williams wrote a great article in Doctor Dobb's Journal years ago about this exact problem using a design where the manager object provides a handle to the client code that the client uses to access the managed object. The manager interposes only on the handle creation and destruction allowing clients with a handle unfettered access to the managed object. (See the recent proposal for standardization for excruciating detail.)

You could apply the approach to your problem fairly easily. First, I'll replicate some parts of the C++11 threads library because they make it MUCH easier to write correct code in the presence of exceptions:

class mutex {
    pthread_mutex_t m_mutex;

    // Forbid copy/move
    mutex(const mutex&); // C++11: = delete;
    mutex& operator = (const mutex&); // C++11: = delete;

public:
    mutex(pthread_mutex_) { pthread_mutex_init(&m_mutex, NULL); }
    ~mutex() { pthread_mutex_destroy(&m_mutex); }

    void lock() { pthread_mutex_lock(&m_mutex); }
    void unlock() { pthread_mutex_unlock(&m_mutex); }
    bool try_lock() { return pthread_mutex_trylock(&m_mutex) == 0; }
};

class lock_guard {
    mutex& mtx;
public:
    lock_guard(mutex& mtx_) : mtx(mtx_) { mtx.lock(); }
    ~lock_guard() { mtx.unlock(); }
};

The class mutex wraps up a pthread_mutex_t concisely. It handles creation and destruction automatically, and saves our poor fingers some keystrokes. lock_guard is a handy RAII wrapper that automatically unlocks the mutex when it goes out of scope.

storage then becomes incredibly simple:

template <class> class handle;

template <class T> class storage
{
private:
   T m_var;
   mutex m_mutex;

public:
   storage() : m_var() {}
   storage(const T& var) : m_var(var) {}

   friend class handle<T>;
};

It's simply a box with a T and a mutex inside. storage trusts the handle class to be friendly and allows it poke around its insides. It should be clear that storage does not directly provide any access to m_var, so the only way it could possibly be modified is via a handle.

handle is a bit more complex:

template <class T>
class handle {
   T& m_data;
   lock_guard m_lock;

public:
   handle(storage<T>& s) : m_data(s.m_var), m_lock(s.m_mutex) {}

   T& operator* () const {
      return m_data;
   }

   T* operator -> () const {
      return &m_data;
   }
};

it keeps a reference to the data item and holds one of those handy automatic lock objects. The use of operator* and operator-> make handle objects behave like a pointer to T.

Since only way to access the object inside storage is through a handle, and a handle guarantees that the appropriate mutex is held during its lifetime, there's no way for client code to forget to lock the mutex, or to accidentally access the stored object without locking the mutex. It can't even forget to unlock the mutex, which is nice as well. Usage is simple (See it working live at Coliru):

storage<int> foo;

void example() {
  {
    handle<int> p(foo);
    // We have exclusive access to the stored int.
    *p = 42;
  }

  // other threads could access foo here.

  {
    handle<int> p(foo);
    // We have exclusive access again.
    *p *= 12;
    // We can safely return with the mutex held,
    // it will be unlocked for us in the handle destructor.
    return ++*p;
  }
}

You would code the program in the OP as:

struct shape_t
{
   int numSides;
   int x;
   int y;
};

int main()
{
   storage<int> intStore;
   storage<shape_t> shapeStore;

   // To set int value I can do:
   *handle<int>(intStore) = 2;

   {
      // To set shape_t value I can do:
      handle<shape_t> ptr(shapeStore);
      ptr->numSides = 2;
      ptr->x = 5;
      ptr->y = 4;
   }

   // To modify 'x' (and keep y and numSides the same) I have to do:
   handle<shape_t>(shapeStore)->x = 5;
}
Casey
  • 41,449
  • 7
  • 95
  • 125
  • It's a very nice solution for handling basic types. But in my case I don't just want to use basic types, like handle I want to handle structs like handle and be able to get to the elements within. (numSides, x, y, etc...). How do we access / modify the elements of the structure with this method? +1 for nice design – code_fodder Sep 29 '14 at 09:13
  • @code_fodder The beauty of the design is that it's completely agnostic to the type that is stored inside. It can be `int`, or `std::queue`, or any arbitrarily complex object type. `handle` is just another kind of smart pointer. – Casey Sep 29 '14 at 15:26
  • ah yes, I see now. Thats very cool :). So I could have: `storage foo; handle p(fpp); p->x = 42;` But I have to declare both storage and handle with same types... – code_fodder Sep 29 '14 at 18:24
  • I have been playing with this and decided that it does not work as I want it. The lock mechanism is too simplified. It locks on creation of a variable and un-locks on destruction. So this is ok for some applications, but I want to operation mutex's on global variables. I have printed out the lock/unlock and it only happens once at the start and end of the program :(, so this is no use to me unfortunately. But still a nice little bit of code. – code_fodder Oct 01 '14 at 14:33
  • @code_fodder I'm not sure what "operation mutex's on global variables" means - perhaps you could edit the question to reflect your requirements? – Casey Oct 01 '14 at 15:01
  • Well, what I mean is let's say we have the member variable `handle m_p(foo);` to some class. Then when the variable is initialised the mutex will be locked, and only when the class is destroyed will it be unlocked. So during the life of the variable it stays locked (so my testing is showing). I think because lock-guard locks at construction and unlocks at destruction time. Do I have it wrong somehow? – code_fodder Oct 02 '14 at 13:50
  • No, you are correct - that's the entire point. You create a handle when you want to access the managed object, and destroy it when you are finished. It handles the locking and unlocking automatically. – Casey Oct 02 '14 at 14:32
  • oh, ok, I see now. You are forcing the scope with the brackets `{ ... }`, got it :) – code_fodder Oct 06 '14 at 08:09
1

I can propose you alternative solution. When you need you can get special template class that allows managing containing object.

template < typename T >
class SafeContainer
{
public:
    // Variadic template for constructor
    template<typename ... ARGS>
    SafeContainer(ARGS ...arguments)
        : m_data(arguments ...)
    {};

    // RAII mutex
    class Accessor
    {
    public:
        // lock when created
        Accessor(SafeContainer<T>* container)
            :m_container(container)
        {
            m_container->m_mutex.lock();
        }

        // Unlock when destroyed
        ~Accessor()
        {
            m_container->m_mutex.unlock();
        }

        // Access methods
        T* operator -> ()
        {
            return &m_container->m_data;
        }

        T& operator * ()
        {
            return m_container->data;
        }

    private:
        SafeContainer<T> *m_container;
    };
    friend Accessor;

    Accessor get()
    {
        return Accessor(this);
    }


private:
    T m_data;

    // Should be using recursive mutex to avoid deadlocks
    std::mutex m_mutex;
};

Example:

struct shape_t
{
    int numSides;
    int x;
    int y;
};

int main()
{
    SafeContainer<shape_t> shape;
    auto shapeSafe = shape.get();
    shapeSafe->numSides = 2;
    shapeSafe->x = 2;
}
Teivaz
  • 5,462
  • 4
  • 37
  • 75
  • +1 for the idea. Effectively this is a two-step get / edit rather then a direct edit (i.e. like a set() function). – code_fodder Sep 29 '14 at 09:19