我有一个类模板,如下所示:
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
数据成员转变为非可变数据成员类的基础?
答案 0 :(得分:7)
不,基类不能是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; }
答案 1 :(得分:5)
您可以使用互斥包装并将其专门用于空互斥锁,然后您可以执行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。
所以在这种情况下,你的resource_lock必须对MutexWrapper采用 const 引用 - 当人们期望(并且正确地)它实际上改变了互斥体的状态时。对于那些不知道如何实现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 :(得分:4)
假设你的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
和其他类型具有良好的复制ctors等。 (因为,他们没有T(T&)
ctor或operator=
感觉过分对rhs的非const
- ness感到过分了。