好的,问题标题有点难以说明。我想要实现的是创建一个带有get / set函数的模板类,它可以处理简单的类型和结构。
这对于整数和字符等类型来说很简单......但是当模板类型为' T'是一个结构然后变得更难。
例如,这里是一个模板类,我省略了它的各个部分(比如构造函数等),但它显示了get / set函数:
编辑:只允许此类修改数据,因此不允许在外部传递引用。原因是我想在set / 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);
不必使用临时变量。这可能吗?如何?
I looked at this answer,但它没有做我想要的,因为它适用于特定的结构类型
答案 0 :(得分:4)
让您的get()
成员返回参考:
T& get()
{
return m_var;
}
然后你可以说
shapeStore.get().x = 42;
注意添加const重载是个好习惯:
const T& get() const
{
return m_var;
}
另请注意,如果您的get
和set
方法确实没有什么特别之处,例如,您可以考虑公开数据并取消使用getter / setter:
template <class T> struct storage
{
T m_var;
};
编辑:如果要允许对成员进行同步更改,则选项是使用具有修改功能的方法。该函数在类中应用,在您的情况下,由互斥锁保护。例如,
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);
答案 1 :(得分:1)
您的设计对于原始类型是相当可行的,但它要求您复制类类型的整个接口,并且很快变得难以管理。即使在原始类型的情况下,您可能希望启用比简单get
和set
更复杂的原子操作,例如increment
或add
或multiply
。简化设计的关键是要意识到您实际上并不想插入客户端代码在数据对象上执行的每一个操作,您只需要在客户端代码原子执行一系列操作之前和之后设置。< / p>
Anthony Williams wrote a great article in Doctor Dobb's Journal年前使用一种设计来解决这个问题,其中manager对象提供客户端用来访问托管对象的客户端代码的句柄。管理器仅插入句柄创建和销毁,允许具有句柄的客户端不受限制地访问托管对象。 (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;
}
}
您可以将OP中的程序编码为:
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;
}
答案 2 :(得分: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;
}