C++ 试图摆脱空数据字段(一种空基优化?)

C++ 试图摆脱空数据字段(一种空基优化?),c++,C++,我想要一个记帐/检测层,如果需要的话,它应该统计对象实例上的许多不同事件。 事件可以是发生的任何事情。例如,在一天结束时,您应该能够判断文件被写入5400次,或者锁被阻塞52456次,未被占用12311次,通过旋转获取33470次,等待超时609次。无论什么你喜欢什么都行 所以,如果发生了什么事情,你可以编写一些计数器,就这样。很简单 问题是,当您关闭该功能时,它不应该向某个类的每个实例添加24或36个或更多字节,因为可能有相当多的实例 基本上,我认为这是简化的,而不是线程安全的: #inclu

我想要一个记帐/检测层,如果需要的话,它应该统计对象实例上的许多不同事件。 事件可以是发生的任何事情。例如,在一天结束时,您应该能够判断文件被写入5400次,或者锁被阻塞52456次,未被占用12311次,通过旋转获取33470次,等待超时609次。无论什么你喜欢什么都行

所以,如果发生了什么事情,你可以编写一些计数器,就这样。很简单

问题是,当您关闭该功能时,它不应该向某个类的每个实例添加24或36个或更多字节,因为可能有相当多的实例

基本上,我认为这是简化的,而不是线程安全的:

#include <type_traits>

struct count // actually counts stuff
{
    int val = 0;
    count& operator++() { ++val; return *this; }
    operator int() { return val; }
};

struct null_count // just the interface
{
    null_count& operator++() { return *this; }
    operator int() { return 0; }
};

template<bool enable> struct someclass
{
    using counter_t = std::conditional_t<enable, count, null_count>;

    counter_t a;
    counter_t b;
    counter_t c;
    counter_t d;
    counter_t e;
    counter_t f;    // might have 10 or 20 of them?!

    void blah()  { ++a; }
    void blubb() { ++b; }
    void foo()   { ++c; }
    void bar()   { ++d; } // whatever
};
毫不奇怪,在本例中,someclass的大小将是24,因为它必须将这些整数存储在某个地方。没关系,毕竟我想要这些。我对这些数字感兴趣

现在,理想情况下,someclass的大小应该是0,或者更确切地说,1,因为这是标准允许我们拥有的最小值。然而,它是6,因为每个计数器的大小当然也必须至少为1,即使其中确实没有任何东西。 如果一个类最初只由一个句柄组成,而您的值超过了两倍或三倍?它的大小仅仅是因为有很多空对象,它们没有做任何有用的事情,你可能有几百或几千个实例,好吧。。。那就糟了。这不是你想要的

从C++20开始就存在[[no_unique_address]],这似乎正是我想要的。不幸的是,到目前为止,编译器支持是,呃。。。令人失望的我的GCC不喜欢它。出于这个原因,我也没有测试过

存在一个特性空基类优化,它几乎永远存在并支持。这听起来似乎可以解决问题。 问题是我可以将计数器移动到基类中,当然。我也可以有条件地从base或empty派生,没有问题

但是,所有递增计数器的代码都是无效的--您很难引用一个根本不存在的成员!。因此,我必须将每个增量都封装在ifdef或类似文件中,这是非常不可取的。或者使用一个宏,该宏在版本中或多或少执行相同的操作,非常相似。尽管这会起作用,但这也是不可取的。 天真地说,有人可能会说:嘿,可以使用if-constexpr,这很好,但不幸的是,它不会编译


有没有另一种直接的方法来优化空数据?

解决方案中的问题是,正如您所知,您有6个独立的成员变量,每个变量至少占用一个字节。更好的方法可能是将计数器存储到数组中,正如@scheff在comments部分中所建议的那样

template <size_t N>
class counters {
   std::array<size_t, N> values{};

public:    
   template <size_t I>
   void inc() {
      static_assert(N > I);
      values[I]++;
   }

   template <size_t I>
   size_t get() const {
      static_assert(N > I);
      return values[I];
   }
};
并按如下方式使用:

void blah()  { inc_cnt<BLAH>(); }
解决方案 将属性应用于空数据成员

笔记 [[no_unique_address]]属性完全由GCC指定。 如果您的GCC版本不支持它,请更新它。 如果您有最新版本的GCC,请报告缺陷或对其进行升级投票。 等待将正确使用[[no_unique_address]]的GCC修补程序。 总结
使用[[无唯一地址]]。它很快就会开始工作。

如果我理解正确,您只需要一个模板结构someclass{};专业化如果没有,我不知道你想要什么;tbh我真的不知道someclass和someclass有什么共同点,也许你只需要一些带计数器的类和一些不带计数器的类,这里不需要模板。为什么要让它们共享一些通用的实现?someclass和someclass是两种完全不相关的类型anyhow@formerlyknownas_463035818啊,我相信op仍然需要编译++a++b和所有类似的东西,但是没有op。这个怎么样。至少,我可以大大减少禁用计数器的占用空间-@达尼朗格看起来很有希望。我喜欢你的回答。我必须承认我的模板编程能力相当弱。所以,你找到了一种改进我的尝试的方法,我并不感到惊讶。我不得不查找我不知道这个术语的EBO,发现它很好地补充了你的答案。这很好。调用有点让人讨厌,因为没有简单的方法可以在基础中使用所有这些模板,至少我记得没有,但我真的很喜欢这样的解决方案。完全按照我的要求执行,并且没有宏。@Damon计数器索引也可以作为函数参数传递,但我更喜欢这种解决方案。首先,编译器可能会生成效率稍高的代码,因为它在编译时知道索引。但更重要的是,索引检查也可以在编译时完成,这比抛出一些运行时错误要好得多。您还可以将模板内容包装到一个单独的函数中,查看更新的答案
就像这样,它甚至看起来令人愉快,令人敬畏。这是由GCC 9支持的,而不是由GCC 8支持的。
template<bool enable, size_t N = enable ? 4 : 0>
class potentially_counted : private counters<N> {
   int handle;  // class' data

   enum { BLAH, BLUBB, FOO, BAR };

public:
   void blah()  { counters<N>::template inc<BLAH>(); }
   void blubb() { counters<N>::template inc<BLUBB>(); }
   void foo()   { counters<N>::template inc<FOO>(); }
   void bar()   { counters<N>::template inc<BAR>(); }
};
int main()
{
  std::cout << "sizeof(potentially_counted<true>): "
     << sizeof(potentially_counted<true>) << std::endl;
  std::cout << "sizeof(potentially_counted<false>): "
     << sizeof(potentially_counted<false>) << std::endl;
}
template <size_t I>
void inc_cnt() { counters<N>::template inc<I>(); }
void blah()  { inc_cnt<BLAH>(); }