Ссылка только на запись в C++?

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

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 ответов

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

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
}

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

// ...
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
  }
}

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

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

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

void set_data(const T& var);

Вот как доступ только для записи применяется в C++.

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


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

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

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

Другие вопросы по тегам