2014-09-25 2 views
1

Хорошо, заголовок вопроса немного сложно сформулировать. Я пытаюсь создать класс шаблона с функциями get/set, которые могут обрабатывать простые типы и структуры.Доступ к элементам типа структуры класса шаблонов

Это просто для типов, таких как целые числа и char, и т. Д. Но когда тип шаблона «Т» является структурой, становится все труднее.

Например, здесь есть шаблонный класс, где я опустил различные части его (например, как конструктор, и т.д.), но он показывает получить/установить функцию:

EDIT: только этот класс разрешено изменять данные, поэтому передача ссылки снаружи не допускается. Причина в том, что я хочу сделать мьютекс вокруг набора/get. Я/есть обновление функции ...

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; 
    } 
}; 

Затем рассмотрим следующий код:

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); 
} 

То, что я хочу быть в состоянии сделать, если его возможно, это установить членов shape_t индивидуально с помощью некоторых средств в классе шаблонов, например:

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

И не нужно использовать temp var. Это возможно? как?

I looked at this answer, но это не совсем то, что я хотел, потому что это для конкретного типа структуры

+0

Это может быть сделано с помощью макросов. Вам нужен пример? – teivaz

ответ

4

Сделайте свой get() член возвращает ссылку:

T& get() 
{ 
    return m_var; 
} 

Тогда можно сказать, что

shapeStore.get().x = 42; 

Замечание: добавлена ​​перегрузка при перегрузке:

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

Также обратите внимание, что если ваш get и set методы действительно не делать ничего особенного, как в вашем примере, вы могли бы рассмотреть общественности данных и покончив с геттеры/сеттеры:

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

Edit: Если вы хотите разрешить синхронизированные изменения для члена, опция должна иметь метод, который принимает модифицирующую функцию. Функция применяется внутри класса, в вашем случае, защищена мьютексом. Например,

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; 
}; 

Затем вы можете изменять элементы в синхронном образом:

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

Если у вас нет C++ 11 вы можете передать функцию вместо:

void foo(shape_t& s) { s.x = 42; } 
shapeStore.do_stuff(foo); 
+0

ах, да, я должен был упомянуть об этом.Я не хочу редактировать значения вне класса, редактирование должно быть внутри класса - в моем случае, потому что я хотел сделать мьютекс вокруг get/set. Я обновлю сообщение, я сделал это слишком просто. Но в остальном, хороший простой ответ :) –

+0

@code_fodder Я добавил альтернативу с мьютексом. – juanchopanza

+0

Это интересно ... хотя я не могу понять, как я буду применять такое, чтобы я мог модифицировать определенные элементы из «внешнего», т. Е. Из main(), вы можете показать пример изменения только «х», coodintate of shape_t? Огромное спасибо! –

1

Ваш дизайн довольно работоспособен для примитивных типов, но для этого требуется реплицировать весь интерфейс типов классов и быстро становится неуправляемым. Даже в случае примитивных типов вы можете включить более сложные атомные операции, чем просто get и set, например., increment или add или multiply. Ключом к упрощению дизайна является осознание того, что вы фактически не хотите вставлять в каждую операцию, которую клиентский код выполняет на объект данных, вам нужно только вставить до и после того, как клиентский код атомарно выполняет последовательность операций.

Anthony Williams wrote a great article in Doctor Dobb's Journal лет назад об этой точной проблеме, используя конструкцию, в которой объект-менеджер предоставляет дескриптор клиентского кода, который клиент использует для доступа к управляемому объекту. Менеджер вмешивается только в создание и уничтожение дескриптора, что позволяет клиентам с неограниченным доступом к управляемому объекту. (See the recent proposal for standardization for excruciating detail.)

Вы можете довольно легко применить подход к своей проблеме. Во-первых, я буду копировать некоторые части библиотеки C++ 11 нитей, потому что они делают это гораздо проще писать правильный код при наличии исключений:

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(); } 
}; 

Класс mutex оборачивает pthread_mutex_t сжато. Он автоматически обрабатывает создание и уничтожение, а также сохраняет наши бедные пальцы на некоторых нажатиях клавиш. lock_guard - удобная оболочка RAII, которая автоматически разблокирует мьютекс, когда он выходит за рамки.

storage становится невероятно просто:

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>; 
}; 

Это просто ящик с T и mutex внутри. storage доверяет классу handle, чтобы быть дружелюбным и позволяет ему совать его внутренности. Должно быть ясно, что storage напрямую не предоставляет доступа к m_var, поэтому единственный способ, который он может изменить, - через handle.

handle немного сложнее:

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; 
    } 
}; 

он хранит ссылку на элемент данных и имеет один из этих удобных автоматических объектов блокировки. Использование operator* и operator-> объектов handle ведет себя как указатель на T.

Поскольку единственный способ получить доступ к объекту внутри storage находится через handle, и через handle гарантирует, что соответствующая mutex проводится в течение его жизни, нет никакого способа для клиентского кода забыть заблокировать мьютекс, или случайно получить доступ к сохраненный объект без блокировки мьютекса. Он даже не может забыть разблокировать мьютекс, что тоже приятно. Использование простой (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; 
    } 
} 

Вы бы код программы в ОП как:

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; 
} 
+0

Это очень приятное решение для обработки базовых типов. Но в моем случае я не просто хочу использовать базовые типы, например handle Я хочу обрабатывать структуры, такие как handle , и иметь возможность доступа к элементам внутри. (numSides, x, y и т. д.). Как мы можем получить доступ/модифицировать элементы структуры с помощью этого метода? +1 для приятного дизайна –

+0

@code_fodder Красота дизайна заключается в том, что он полностью агностик для типа, который хранится внутри. Это может быть 'int', или' std :: queue 'или любой произвольно сложный тип объекта. 'handle ' - это еще один вид умного указателя. – Casey

+0

Да, я вижу сейчас. Это очень круто :). Поэтому я мог бы: 'storage foo; ручка p (fpp); p-> x = 42; 'Но я должен объявить как хранилище, так и дескриптор с теми же типами ... –

1

Я могу предложить вам альтернативное решение. Когда вам нужно, вы можете получить специальный класс шаблонов, который позволяет управлять содержащим объектом.

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; 
}; 

Пример:

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; 
} 
+0

+1 для этой идеи. Эффективно это двухступенчатое получение/редактирование, а не прямое редактирование (т. Е. Как функция set()). –

Смежные вопросы