1

有没有办法编写对对象的只写引用?例如,假设有一个互斥类:

template <class T> class mutex {
protected:
   T _data;
public:
   mutex();
   void lock(); //locks the mutex
   void unlock(); //unlocks the mutex
   T& data(); //returns a reference to the data, or throws an exception if lock is unowned
};

有没有办法保证一个人不能这样做:

mutex<type> foo;
type& ref;
foo.lock();
foo.data().do_stuff();
ref = foo.data();
foo.unlock();
//I have a unguarded reference to foo now

另一方面,这是否值得?我知道有些人认为程序员不会故意破坏系统,但是,为什么我们首先要有私有变量,嗯?只是说它是“未定义的行为”会很好,但这似乎有点太不安全了。

编辑:好的,我理解 setter 例程的想法,但这将如何实现?

mutex<vector<int> > foo;
foo.lock();
for (int i=0; i < 10; i++) {
   foo.data().push_back(i);
}

foo.unlock(); 使用 set 例程将需要每次写入的副本:

mutex<vector<int> > foo;
foo.lock();
for (int i=0; i < 10; i++) {
   vector<int> copy = foo.read();
   copy.push_back(i);
   foo.write(copy);
}

尽管您可以在这种特定情况下进行微不足道的优化,例如,如果几个不同的线程都在推动元素,甚至可能擦除一些元素,这可能会变成相当多的内存复制(即每个关键部分一个)。

4

5 回答 5

2

是的,您可以创建一个在调用 unlock 时失效的包装器类并返回包装器,而不是返回引用,并且您可以重载其赋值运算符以分配给引用。诀窍是您需要保留对包装器内部数据的引用,以便在调用解锁时,在释放锁之前使您创建的任何包装器无效。

于 2010-04-23T00:09:48.057 回答
2

区分 getter 和 setter 的常用方法是通过对象的 const-ness:

template <class T> class mutex {
public:
   mutex();
   void lock();
   void unlock();
         T& data();       // cannot be invoked for const objects
   const T& data() const; // can be invoked for const objects
protected:
   T _data;
};

现在,如果您想拥有只读访问权限,请将互斥锁设为 const:

void read_data(const mutex< std::vector<int> >& data)
{
   // only const member functions can be called here
}

您可以将非常量对象绑定到 const 引用:

// ...
mutex< std::vector<int> > data;
data.lock();
read_data(data);
data.unlock();
// ...

请注意,lock()andunlock()函数在面对异常时本质上是不安全的:

void f(const mutex< std::vector<int> >& data)
{
  data.lock();
  data.data().push_back(42); // might throw exception
  data.unlock(); // will never be reached in push_back() throws
}

解决这个问题的常用方法是RAII(资源获取是初始化):

template <class T> class lock;

template <class T> class mutex {
public:
   mutex();
protected:
   T _data;
private:
   friend class lock<T>;
   T& data();
   void lock();
   void unlock();
};

template <class T> class lock {
public:
  template <class T> {
  lock(mutex<T>& m) m_(m) {m_.lock();}
  ~lock()                 {m_.unlock();}

         T& data()        {return m_.data();}
   const T& data() const  {return m_.data()}
private:
  mutex<T>& m_;
};

请注意,我还将访问器函数移至锁定类,因此无法访问未锁定的数据。

您可以像这样使用它:

void f(const mutex< std::vector<int> >& data)
{
  {
    lock< std::vector<int> > lock_1(data);
    std::cout << lock1.data()[0]; // fine, too
    lock1.data().push_back(42);   // fine
  }
  {
    const lock< std::vector<int> > lock_2(data); // note the const
    std::cout << lock1.data()[0];  // fine, too
    // lock1.data().push_back(42); // compiler error
  }
}
于 2010-04-23T05:48:46.773 回答
1

您可以将数据封装为私有数据并公开写入例程。在该例程中,您可以锁定互斥锁,从而为您提供与您所追求的类似的行为。

于 2010-04-22T23:55:51.810 回答
0

您可以使用成员函数,如下所示:

void set_data(const T& var);

这就是在 C++ 中应用只写访问的方式。

于 2010-04-22T23:53:34.123 回答
-2

不,没有办法保证在不安全的语言(如 C++)中读取和写入内存,所有内存都被视为一个大数组。


[编辑]不知道为什么所有的反对票;这是正确和相关的。

在 Java 或 C# 等安全语言中,您当然可以保证,例如,正确实现的不可变类型将保持不可变。在 C++ 中永远无法做出这样的保证。

担心的不是恶意用户,而是意外的无效指针;我曾参与过 C++ 项目,其中不可变类型由于完全不相关的代码中的无效指针而发生了变异,从而导致极难追踪的错误。这种保证——只有安全的语言才能做到——既有用又重要。

于 2010-04-22T23:54:18.810 回答