Могу ли я очистить базу с помощью изменяемых данных?
У меня есть шаблон класса, который выглядит так:
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
в базу неперемещаемого класса элементов данных?
Ответы
Ответ 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)