【问题标题】:Can I empty-base optimize mutable data?我可以空基优化可变数据吗?
【发布时间】:2015-02-24 11:51:35
【问题描述】:

我有一个如下所示的类模板:

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_mutexsome_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_mutexmutable,我不知道如何在不创建the_data 以及所有数据mutable 的情况下使其成为基类,从而完全接管编译器的责任保护我免受愚蠢的 constness 错误。

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

【问题讨论】:

  • 元组和 5 个引用访问器?
  • 你可以编写一个包装类模板mutable_if_nonempty&lt;T&gt;,它提供一个T&amp; get() const;成员函数IFFT是非空的(通过存储一个mutable T t;),否则一个T const&amp; get() const;成员函数(通过继承T)。或者,如果T 为空,我认为const_cast&lt;T&amp;&gt;(*this) 是安全的,允许在从T 继承时实现T&amp; get() const;

标签: c++ optimization mutable


【解决方案1】:

不,基类不能是mutable。但是……

从而完全接管编译器的责任,以保护我免受愚蠢的 constness 错误。

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

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

【讨论】:

  • 这里有两个赞成聪明,一个反对混淆。 :)(我认为这可行,但它有点笨拙,并且需要向任何维护者解释一个大小合适的注释。)
  • @sbi 嘿,是的,评论是合适的。但是像“// Use inheritance instead of multiple members to reduce class size when instantiated with empty dummy policy and/or mutex. Mutex is supposed to be mutable, unfortunately this means all data is technically mutable.”这样简单的就足够清楚了,不是吗?
【解决方案2】:

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

【讨论】:

  • 我无法更改锁类。 (还有不止一个虚拟互斥类,但有模板元魔法可以在编译时检测它们,所以我可以专门化MutexWrapper。)
  • @sbi: 好吧,你可以对 this 指针进行 const_cast(我会将它包装在一个成员函数中),但这越来越难看。
  • @sbi:实际上我刚刚意识到,如果你必须在某个时候进行 const 转换,那么整个包装器的想法或多或少是无用的(请参阅我的上一次编辑)
  • 我最终使用了一个变体,我在其中创建了虚拟互斥锁 lock()/unlock() 函数 const。这也需要在锁类中使用更多的模板魔法,但我现在可以正常工作了。既然你最接近我最后所做的,我会接受这个答案。
  • @sbi:谢谢,如果不涉及太多代码,您能否展示一下您的最终解决方案的实际样子?
【解决方案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&amp;&amp; 不在游戏中。这还假设T 和其他类型具有行为良好的复制ctor 等。 (例如,他们没有 T(T&amp;) ctor 或 operator= 感觉过度授权的非const-ness rhs)

【讨论】:

    猜你喜欢
    • 2011-06-05
    • 2011-09-30
    • 2013-03-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多