Подтвердить что ты не робот

Могу ли я очистить базу с помощью изменяемых данных?

У меня есть шаблон класса, который выглядит так:

template<typename T, typename Mutex, typename SomePolicy>
class my_class {
public:
  T f() const {
    resource_lock a_lock(some_mutex);
    return some_policy.some_operation(some_data);
  }
private:
  T             some_data;
  mutable Mutex some_mutex;
  SomePolicy    some_policy;
};

Если не использовать одновременно, у нас есть фиктивный тип мьютекса, который имеет все функции-члены как встроенные пустые функции и никаких данных. Существуют политики, которые имеют данные для каждого экземпляра и те, у которых нет данных.

Это код библиотеки, и оказывается, что этот шаблон шаблона используется в коде приложения, где важны дополнительные байты, которые необходимы для членов данных some_mutex и some_policy, даже когда они являются пустыми классами. Поэтому я хочу использовать пустую оптимизацию базы. Для политики это легко:

template<typename T, typename Mutex, typename SomePolicy>
class my_class {
public:
  T f() const {
    resource_lock a_lock(the_data.some_mutex);
    return the_data.some_operation(the_data.some_data);
  }
private:
  struct data : SomePolicy {
    T             some_data;
    mutable Mutex some_mutex;
  };
  data the_data;
};

Однако, учитывая, что some_mutex является mutable, я не знаю, как сделать его базовым классом, не делая the_data, и, следовательно, все данные mutable, тем самым полностью перехватывая ответственность компилятора за защитите меня от глупых ошибок.

Есть ли способ превратить элемент данных mutable в базу неперемещаемого класса элементов данных?

4b9b3361

Ответ 1

Что вы можете сделать, это использовать оболочку mutex и специализировать ее для пустого мьютекса, для которого вы затем можете выполнить EBCO.

class EmptyMutex{
    void lock() const {};
    void unlock() const {};
};

template< class MUX>
class MutexWrapper {
    mutable MUX mux;
public:
    void lock() const {mux.lock();};
    void unlock() const { mux.unlock() ;};
};

template<>
class MutexWrapper<EmptyMutex> : public EmptyMutex {};


template<typename T, typename Mutex, typename SomePolicy>
class my_class {
public:
    T f() const {
        resource_lock a_lock(the_data);
        return the_data.some_operation(the_data.some_data);
    }
private:
    struct data : SomePolicy ,MutexWrapper<Mutex> {
        T             some_data;

    };
    data the_data;
};

Предостережение этого решения состоит в том, что - внутри функции-члена const - если вы можете напрямую использовать функции lock() и unlock(), вы можете передавать только ссылки const в MutexWrapper в качестве параметров. Таким образом, в этом случае ваш ресурс должен будет использовать ссылку const для MutexWrapper - когда можно было бы ожидать (и это правильно), что он фактически изменяет состояние мьютекса. Это довольно проблески для кого-то, кто не знает, как MutexWrapper реализовано.

По этой причине я считаю, что более разумно просто const_cast использовать мьютекс, когда это необходимо, вместо использования обертки:

template<typename T, typename Mutex, typename SomePolicy>
class my_class {
public:
    T f() const {
        resource_lock a_lock(getNonConstMuxRef());
        return the_data.some_operation(the_data.some_data);
    }   
private:
    struct data : SomePolicy, Mutex {
        T  some_data;
    };
    data the_data;
    Mutex& getNonConstMuxRef() const { return const_cast<my_class<T, Mutex, SomePolicy>*>(this)->the_data; }
};

Ответ 2

Нет, базовый класс не может быть mutable. Но...

тем самым полностью взяв на себя ответственность компилятора, чтобы защитить меня от глупых ошибок в константах.

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

mutable struct : SomePolicy, Mutex {
  T some_data;
} _dont_use_directly;

T &some_data() { return _dont_use_directly.some_data; }
const T &some_data() const { return _dont_use_directly.some_data; }

SomePolicy &some_policy() { return _dont_use_directly; }
const SomePolicy &some_policy() const { return _dont_use_directly; }

Mutex &some_mutex() const { return _dont_use_directly; }

Ответ 3

Предполагая, что ваш std::tuple реализует пустую оптимизацию базы (проверьте), это может помочь:

mutable std::tuple<T, Mutex, SomePolicy> raw;
T          const& data()   const { return std::get<0>(raw); }
T               & data()         { return std::get<0>(raw); }
Mutex           & mutex()  const { return std::get<1>(raw); }
SomePolicy const& policy() const { return std::get<2>(raw); }
SomePolicy      & policy()       { return std::get<2>(raw); }

в основном мы помещаем оптимизацию в член .raw mutable, к которому мы никогда не обращались (в качестве бонуса, доступ к кортежам беспорядочен). Затем мы создаем ссылочные-аксессоры, которые обеспечивают const.

Вы также можете:

my_class(my_class const&  )=default;
my_class(my_class      && )=default;
my_class&operator=(my_class const&  )=default;
my_class&operator=(my_class      && )=default;

чтобы быть явным, что my_class const&& не находится в игре. Это также предполагает, что T и другие типы имеют хорошо выполненные копии и т.д. (как и в, у них нет T(T&) ctor или operator=, который чувствует себя чрезмерно озаглавленным о const -ness rhs)