Ссылка только на запись в C ++? - PullRequest
1 голос
/ 23 апреля 2010

Есть ли способ кодировать ссылку только для записи на объект? Например, предположим, что существует класс мьютекса:

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

С другой стороны, стоит ли оно того? Я знаю, что некоторые люди предполагают , что программисты не будут намеренно забивать систему, но тогда почему у нас в первую очередь закрытые переменные, а? Было бы неплохо просто сказать, что это «неопределенное поведение», но это кажется слишком небезопасным.

РЕДАКТИРОВАТЬ: ОК, я понимаю идею рутины сеттера, но как это будет сделано?

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

хотя вы могли бы тривиально оптимизировать в этом конкретном случае, если, скажем, несколько различных потоков являются элементами выталкивания, и, возможно, даже стирают несколько, это может стать довольно избыточным копированием памяти (т. Е. По одному на критический раздел).

Ответы [ 5 ]

2 голосов
/ 23 апреля 2010

Распространенным способом различения геттеров и сеттеров является постоянство объекта:

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

Теперь, если вы хотите иметь доступ только для чтения, сделайте mutex const:

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

Вы можете привязать неконстантный объект к константной ссылке:

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

Обратите внимание, что функции lock() и unlock() по своей сути небезопасны перед лицом исключений:

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
  }
}
2 голосов
/ 23 апреля 2010

Да, вы можете создать класс-оболочку, который становится недействительным при вызове unlock, и возвращать оболочку вместо возврата ссылки, и вы можете перегрузить его оператор присваивания, чтобы назначить ссылку. Хитрость заключается в том, что вам нужно повесить ссылку на внутренние данные оболочки, чтобы при вызове разблокировки перед снятием блокировки вы аннулировали все созданные вами оболочки.

1 голос
/ 23 апреля 2010

Вы можете инкапсулировать данные как частные и предоставить процедуру записи.В рамках этой процедуры вы можете заблокировать свой мьютекс, давая вам поведение, подобное тому, за которое вы стреляете.

0 голосов
/ 23 апреля 2010

Нет.Невозможно гарантировать что-либо о чтении и записи памяти в небезопасных языках, таких как C ++, где вся память обрабатывается как один большой массив.


[Edit] Не знаю почемувсе отрицательные голоса;это правильно и актуально.

В безопасных языках, таких как Java или C #, вы, безусловно, можете гарантировать, что, например, правильно реализованные неизменяемые типы останутся неизменными.Такую гарантию никогда нельзя дать в C ++.

Страх - это не столько злонамеренные пользователи, сколько случайные недействительные указатели;Я работал над проектами на C ++, в которых неизменяемые типы были видоизменены из-за неверного указателя в полностью несвязанном коде, в результате чего ошибки, которые чрезвычайно трудно отследить.Эта гарантия, которую могут дать только безопасные языки, полезна и важна.

0 голосов
/ 23 апреля 2010

Вы можете использовать функцию-член следующим образом:

void set_data(const T& var);

Это способ доступа только для записи в C ++.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...