5

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

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 标志来获取警告。所以我仍然不知道这有多不安全。回想起来,我觉得它一定不是一个好主意……尽管如果我能侥幸逃脱它会非常有用。

4

3 回答 3

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++ 的适用性如何。但只要你从不子类化你的类就可以了。

于 2013-01-18T00:55:55.777 回答
2

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

[C++11: 8.3.4/1]: [..]如果常量表达式(5.19) 存在,它应该是一个整数常量表达式,并且它的值应该大于零。常量表达式指定数组(元素数量)的边界。如果常量表达式的值为N,则数组N元素编号0N-1,标识符的类型D为“<em>derived-declarator-type-list array of NT”。[..]

出于这个原因,您的类模板不能0,0 在 GCC 4.1.2中或在 GCC 4.7.2中使用合理的标志来实例化:

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

  EncapsulatedObjectBase();

  ~EncapsulatedObjectBase();

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

int main()
{
   EncapsulatedObjectBase<0,0> obj;
}

t.cpp:在 'EncapsulatedObjectBase<0, 0>' 的实例化中:
t.cpp:17:从此处实例化第 10 行:错误:ISO C++ 禁止 因 -Wfatal 错误而终止
零大小数组编译。

铿锵 3.2 说:

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

(请注意,在任何情况下,除非您尝试实例化这样的类,否则您不会收到任何错误。)

那么,这是个好主意吗?不,不是。当任一参数为0. 我还会看看为什么你想要零长度数组并考虑调整你的设计。

于 2013-01-18T01:09:33.840 回答
1

1)添加到您的类C++11 static_assertBOOST_STATIC_ASSERT的声明中,您将获得零长度数组的编译时诊断:

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

2) 使用std::arrayboost::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];
};

备注: 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>
   {
     ....
   };
于 2013-01-18T02:39:11.690 回答