我可以空基优化可变数据吗?

Can I empty-base optimize mutable data?

我有一个 class 模板,如下所示:

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

如果不同时使用,我们有一个虚拟互斥锁类型,它的所有成员函数都是内联的空函数,没有数据。有些策略具有每个实例数据,有些策略没有任何数据。

这是库代码,事实证明这个 class 模板被用在应用程序代码中,其中额外的字节很重要,数据成员 some_mutexsome_policy 甚至当它们为空时 classes。所以我想利用空基优化。对于政策,这很简单:

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_mutexmutable,我不知道如何在不制作 the_data 的情况下将其作为基础 class,因此所有数据, mutable,从而完全接管了编译器保护我免受愚蠢的常量错误的责任。

有没有办法将 mutable 数据成员转换为非可变数据成员的 class 的基数?

不,碱基 class 不能是 mutable。但是...

thereby completely taking over the compiler's responsibility to protect me from silly constness mistakes.

...不一定是这样的结果。您仍然可以让编译器帮助您,方法是创建访问函数而不是直接使用您的数据结构。你可以用这样的方式命名它,让每个人都清楚这些访问函数是唯一受支持的数据接口。

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

您可以做的是使用互斥锁包装器并将其专门用于空互斥锁,然后您可以对其执行 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 必须采用 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; }
};

假设您的 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 有过分的权利)