【问题标题】:C++ zero-size array that requires no memory space?不需要内存空间的 C++ 零大小数组?
【发布时间】:2013-06-06 20:47:14
【问题描述】:

当声明一个模板类的成员变量时,有没有办法根据某些模板参数的值使其需要零内存?

一个例子是定义类似std::array<T,n> 的东西,当n==0 时需要零空间。

例如:

template<int num_optional_args> class C {
    int some_variable;
    std::array<int,num_optional_args> optional_args;
};

num_optional_args==0 时有没有办法消除 optional_args 的开销?

std::array&lt;T,n&gt; 的大多数实现为一个 T 元素保留空间,即使在 n==0 时也是如此。

还有其他方法可以保留零空间吗? 为什么这不是 C++ 标准的一部分?

【问题讨论】:

  • 该标准防止类的大小为零,因此您永远不能让sizeof(C&lt;0&gt;) 成为0。除此之外,当模板参数为0 时提供一个特化,你可以对这种情况的类定义做任何你想做的事情。
  • “保留零空间”这句话是什么意思?
  • @GManNickG:我认为sizeof (C&lt;N+1&gt;) == sizeof (C&lt;N&gt;) + sizeof (int) 代表所有非负数N。现在它只适用于N &gt;= 1。问题是将其扩展到N == 0

标签: c++ arrays templates memory-management stdarray


【解决方案1】:

您可以专门化您的类型,以便当数字为零时optional_args 不存在。如果您需要对象存在,那么对象可以存在并可以引用而实际上不占用空间的唯一方法是通过空基类优化。

您可以通过以下方式使用它:

template<int num_optional_args>
class optional_args {
    std::array<int,num_optional_args> args
public:
    // whatever interface you want for the optional args.
    void foo(int n) {
        if (n < num_optional_args)
            args[n];
        throw std::runtime_error("out of range");
    }
};

template<>
class optional_args<0> {
public:
    // whatever interface you want for the optional args, specialized for 0 args.
    void foo(int n) {
        throw std::runtime_error("out of range");
    }
};

template<int num_optional_args>
class C : optional_args<num_optional_args> {
    int some_variable;
    void bar() {
        for (int i=0; i<num_optional_args; ++i) {
            optional_args::foo(i);
        }
    }
};

【讨论】:

    【解决方案2】:

    您要么需要为至少一个元素保留空间,要么保留指向该元素的指针。 不可能有一个占用内存的数组结构。

    以下结构在创建时仅使用一个 int 和一个指针,这与您将获得的接近于零的值差不多:

    template<typename T>
    class array {
    
      int sz;
      T *head;
    
    };
    

    除此之外,在类定义中要求零空间的概念是愚蠢的。希望它在实例化时占用接近零的空间可能是有意义的,并且可以通过如下参数化构造函数来完成:

    template<typename T>
    class array {
    
      int sz;
      T *head;
    
      array(int n) {
          if (n == 0) return;
          head = new T[n];
      }
    };
    

    【讨论】:

      【解决方案3】:

      正如 Praetorian 所说,您可以专门针对 0。如果您希望 C 类的所有变体具有相同的接口,您可以让所有 C 派生自 C,如下所示: 模板类 C;

      template <> class C<0> {                                                                                                                                                                                                                     
      
          int s;                                                                                                                                                                                                                                   
      
      public:                                                                                                                                                                                                                                      
          int blah();                                                                                                                                                                                                                              
      };                                                                                                                                                                                                                                           
      
      template <int N> class C : public C<0>{                                                                                                                                                                                                      
      
          int a[N];
      };
      
      int C<0>::blah() {return s;}
      
      int main() {
      
          C<1> a;
          C<0> b;
          a.blah();
          b.blah();
          return 0;
      }
      

      【讨论】:

        【解决方案4】:

        如果你不关心你的东西的 POD-ness,你可以使用 Boost.CompressedPair:

        template<int num_optional_args> class C {
          boost::compressed_pair<int, std::array<int,num_optional_args>> data;
          int& some_variable() { return data.first(); }
          std::array<int,num_optional_args>& optional_args() { return data.second(); }
        };
        

        如果 std::array 很可能是一个空类,那应该消除开销。但是你的类不再是 POD,因为compressed_pa​​ir 不是。

        【讨论】:

          【解决方案5】:

          我真的不记得它在 C++ 中是否完全合法,但我认为它仍然在 C 中:您可以拥有一个大小为零的数组,但它必须是结构定义的最后一个成员。从历史上看,它被用于可变长度的缓冲区:

          struct buffer
          {
              usigned int size;
              byte data[0];
          };
          

          buf.data 是完全可用的数组'n'指针,所以如果你注意和malloc N+sizeof(int) 字节,那么你可以将它转换为buffer,将size 设置为N 在这里,你有一个带有 size 前缀的 N 字节 data 数组。关键是每个这样的缓冲区总是有并以'size'前缀开头,然后有数据,所以你可以将每个这样的缓冲区转换为buffer并检查大小,然后使用*(data+x)或data[ x] 假设 0

          参见例如http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

          但是,请注意这是 C,而不是 C++,但我几乎可以肯定我在 C++ 的 POD 中也看到了这种原始内存技巧。

          除此之外,最重要的一点是这样的数组将具有零字节长度。 sizeof(buffer) == sizeof(int) 在上面的例子中,但仅当数组是最后一个成员时。如果您要在数组之后添加另一个字段,那么数组和最后一个字段将需要具有不同的偏移量,因此零长度数组最终将成为 1 字节(+对齐)只是为了具有不同地址。更不用说没有理智的编译器允许您在结构中间声明零长度数组。真的只能作为结构体的尾部。

          【讨论】:

          • 在 C++ 中,明确禁止零大小的数组。一些编译器允许它们作为扩展。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-05-16
          • 1970-01-01
          • 2018-09-25
          • 1970-01-01
          • 2021-04-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多