成员数组的大小由模板参数定义,但为什么没有针对零大小数组的警告?

时间:2013-01-18 00:52:08

标签: c++ arrays templates

我试图编写一个模板化的基类来存储固定数量的数据类型,每种类型都有不同的长度。这是我试图做的很简单的版本:

template< int NINT, int NR0 >
class EncapsulatedObjectBase
{
   public:

  EncapsulatedObjectBase();

  ~EncapsulatedObjectBase();

  double m_real[NR0];
  int m_int[NINT];
}

是的......所以模板参数可以为零,从而声明一个零长度的对象数组。这个库将有多个派生类,每个派生类定义自己的变量数。我有两个问题:

1)这种方法是否存在根本缺陷?

2)如果是这样的话......当我实例化零长度数组时,为什么icc13或gcc4.7.2没有给我警告?对于gcc我使用-wall和-wextra -wabi。缺乏警告让我觉得这种事情还行。

编辑:

以下是显示我所说内容的文件内容:

#include <iostream>

template< int NINT, int NR0 >
class EncapsulatedObjectBase
{
public:
  EncapsulatedObjectBase(){}
  ~EncapsulatedObjectBase(){}

  double m_real[NR0];
  int m_int[NINT];
};


class DerivedDataObject1 : public EncapsulatedObjectBase<2,0>
{
   public:

   DerivedDataObject1(){}

  ~DerivedDataObject1(){}

  inline int& intvar1() { return this->m_int[0]; }
  inline int& intvar2() { return this->m_int[1]; }

};


class DerivedDataObject2 : public EncapsulatedObjectBase<0,2>
{
   public:

   DerivedDataObject2(){}

  ~DerivedDataObject2(){}

  inline double& realvar1() { return this->m_real[0]; }
  inline double& realvar2() { return this->m_real[1]; }
};




int main()
{
   DerivedDataObject1 obj1;
   DerivedDataObject2 obj2;

   obj1.intvar1() = 12;
   obj1.intvar2() = 5;

   obj2.realvar1() = 1.0e5;
   obj2.realvar2() = 1.0e6;

   std::cout<<"obj1.intvar1()  = "<<obj1.intvar1()<<std::endl;
   std::cout<<"obj1.intvar2()  = "<<obj1.intvar2()<<std::endl;
   std::cout<<"obj2.realvar1() = "<<obj2.realvar1()<<std::endl;
   std::cout<<"obj2.realvar2() = "<<obj2.realvar2()<<std::endl;


}

如果我用“g ++ -Wall -Wextra -Wabi main.cpp”编译它,我没有得到任何警告。我必须使用-pedantic标志来获取警告。所以我仍然不知道这是多么不安全。回想起来,我觉得它一定不是一个好主意......虽然如果我能逃脱它会非常有用。

3 个答案:

答案 0 :(得分:3)

在C中,使用零大小的数组作为结构的最后一个成员实际上是合法的,并且通常在结构将以某种动态创建的内联数据结束时使用,这些数据在编译时是未知的。换句话说,我可能会有像

这样的东西
struct MyData {
    size_t size;
    char data[0];
};

struct MyData *newData(size_t size) {
    struct MyData *myData = (struct MyData *)malloc(sizeof(struct MyData) + size);
    myData->size = size;
    bzero(myData->data, size);
    return myData;
}

现在可以将myData->data字段作为指向动态大小数据

的指针进行访问

那就是说,我不知道这种技术对C ++有多适用。但只要你从不继承你的班级,它就可能很好。

答案 1 :(得分:1)

零大小的数组在C ++中实际上是非法的:

  

[C++11: 8.3.4/1]: [..] 如果常量表达式(5.19)存在,它应该是一个整数常量表达式及其值应大于零。常量表达式指定数组中的(元素数)的边界。如果常量表达式的值为N,则数组具有编号为N0的{​​{1}}个元素,N-1的标识符类型为“ {em> derived-declarator-type-list D T“数组。 [..]

出于这个原因,您的类模板无法使用参数N in GCC 4.1.2in GCC 4.7.2进行实例化,并使用合理的标记:

0,0
  

t.cpp:实例化'EncapsulatedObjectBase&lt; 0,0&gt;':   t.cpp:17:从这里实例化   第10行:错误:ISO C ++禁止零大小的阵列
  由于-Wfatal-errors而导致编译终止。

clang 3.2说:

  

source.cpp:10:17:警告:零大小数组是扩展名[-Wzero-length-array]

(请注意,在任何情况下,在尝试实例化此类之前,不会出现任何错误。)

那么,这是个好主意吗?不,不是真的。当任一参数为template< int NINT, int NR0 > class EncapsulatedObjectBase { public: EncapsulatedObjectBase(); ~EncapsulatedObjectBase(); double m_real[NR0]; int m_int[NINT]; }; int main() { EncapsulatedObjectBase<0,0> obj; } 时,我建议禁止对您的类模板进行实例化。我还会看看为什么你想要零长度数组并考虑调整你的设计。

答案 2 :(得分:1)

1)添加到您的类 C ++ 11 static_assert BOOST_STATIC_ASSERT 的声明,您将获得编译时诊断的零长度阵列:

....
   BOOST_STATIC_ASSERT(NR0 > 0);
   BOOST_STATIC_ASSERT(NINT > 0);
   double m_real[NR0];
   int m_int[NINT];
};

2)使用 std :: array boost :: array ,您将在此类代码中对索引溢出问题进行运行时诊断(在调试模式下):

   BOOST_STATIC_ASSERT(NR0 > 0);
   BOOST_STATIC_ASSERT(NINT > 0);
   boost::array<double, NR> m_real;   //double m_real[NR0];
   boost::array<int, NINT> m_int;     //int m_int[NINT];
};

注: class boost :: array 具有零大小数组的专业化

3)对于数组大小,使用 size_t 但不使用int。

你的设计非常危险:

   DerivedDataObject1 a;
   a.m_real[2] = 1;   // size of m_real == 0 !!!

我认为更改类EncapsulatedObjectBase的设计会更好。可能会更好地使用:

   template<typename T, size_t N> class EncapsulatedObjectBase
   {
    ....
   };
   class DerivedDataObject1 : public EncapsulatedObjectBase<int,2>
   {
     ....
   };
   class DerivedDataObject2 : public EncapsulatedObjectBase<double,2>
   {
     ....
   };
   class DerivedDataObject3 : public EncapsulatedObjectBase<double,2>
                            , public EncapsulatedObjectBase<int,2>
   {
     ....
   };