C ++在多大程度上是一种静态类型的语言?

时间:2013-04-10 23:07:39

标签: c++ c++11 language-lawyer static-typing dynamic-typing

我曾经认为这个问题的答案是“ 100%”,但我最近指出了一个值得思考两次的例子。考虑声明为具有自动存储持续时间的对象的C数组:

int main()
{
    int foo[42] = { 0 };
}

此处,foo的类型显然是int[42]。相反,请考虑这种情况:

int main()
{
    int* foo = new int[rand() % 42];
    delete[] foo;
}

此处,foo的类型为int*,但如何在编译时告诉new表达式创建的对象的类型? (重点是强调我不是在讨论new表达式返回的指针,而是讨论由new表达式创建的数组对象

这就是C ++ 11标准的第5.3.4 / 1段规定了new表达式的结果:

  

[...]由 new-expression 创建的实体具有动态存储持续时间(3.7.4)。 [注意:这样的寿命   实体不一定限于创建它的范围。 -end note]如果实体是非数组   对象, new-expression 返回指向所创建对象的指针。如果是数组,那么 new-expression   返回指向数组初始元素的指针。

我曾经认为在C ++中所有对象的类型都是在编译时确定的,但上面的例子似乎反驳了这种看法。另外,根据第1.8 / 1段:

  

[...]对象的属性在对象时确定   已创建。对象可以有一个名称(第3条)。对象具有影响的存储持续时间(3.7)   它的寿命(3.8)。对象具有类型(3.9)。 [...]

所以我的问题是:

  1. 上一段引用中的“ properties ”是什么意思?显然,对象的名称不能算作“创建对象时”的内容 - 除非“ created ”在这里意味着与我想的不同;
  2. 是否有其他类型仅在运行时确定类型的对象示例?
  3. 说C ++是一种静态类型语言在多大程度上是正确的?或者更确切地说,在这方面对C ++进行分类的最恰当方式是什么?
  4. 如果任何人至少可以详细说明上述一点,那就太棒了。

    修改

    标准似乎清楚地表明new表达式确实创建了一个数组对象,而不仅仅是一些像某些人指出的数组一样的对象。根据第5.3.4 / 5段(由Xeo提供):

      

    当分配的对象是数组时(即使用 noptr-new-declarator 语法或 new-type-id 或    type-id 表示数组类型),new-expression产生指向数组初始元素(如果有)的指针。   [注意:new intnew int[10]都有int*类型,new int[i][10]类型为int (*)[10]   -end note] noptr-new-declarator 中的 attribute-specifier-seq 附加到关联的数组类型

3 个答案:

答案 0 :(得分:9)

new-expression 不会创建具有运行时变化数组类型的对象。它创建了许多对象,每个对象都是静态类型int。静态地不知道这些对象的数量。


C ++为动态类型提供了两种情况(第5.2.8节):

  • 与表达式的静态类型相同
  • 当静态类型是多态的时,最派生对象的运行时类型

这些都没有给new int[N]创建的任何对象提供动态数组类型。


讽刺地,对 new-expression 的评估会创建无数个重叠的数组对象。从3.8p2开始:

  

[注意:一旦获得具有适当大小和对齐的存储,数组对象的生命周期就开始,并且当数组占用的存储被重用或释放时,其生命周期结束。 12.6.2描述了基础和成员子对象的生命周期。 - 结束说明]

因此,如果您想谈谈new int[5]创建的“数组对象”,您不仅要提供int[5]类型,还要提供int[4]int[1]char[5*sizeof(int)]struct s { int x; }[5]

我认为这相当于说运行时不存在数组类型。对象的类型应该是限制性的,信息,并告诉你它的属性。允许将内存区域视为具有不同类型的无限数量的重叠数组对象,这意味着该数组对象是完全无类型的。运行时类型的概念仅对存储在数组中的元素对象有意义。

答案 1 :(得分:8)

术语“静态类型”和“动态类型”适用于表达式。

  

静态类型

     

在不考虑执行语义的情况下分析程序得到的表达式(3.9)的类型


  

动态类型

     

< glvalue>由glvalue表达式表示的glvalue引用的最派生对象(1.8)的类型

此外,您可以看到动态类型仅在静态类型可以派生时与静态类型不同,这意味着动态数组类型始终与表达式的静态类型相同。

所以你的问题:

  

但是如何在编译时告诉新表达式创建的对象的类型?

对象具有类型,但它们不是“静态”或“动态”类型,缺少引用该对象的表达式。给定一个表达式,静态类型在编译时始终是已知的。在没有派生的情况下,动态类型与静态类型相同。

但是你要问的是与表达式无关的对象类型。在示例中,您已经要求创建一个对象,但是您没有指定要在编译时创建的对象类型。你可以这样看:

template<typename T>
T *create_array(size_t s) {
    switch(s) {
        case 1: return &(*new std::array<T, 1>)[0];
        case 2: return &(*new std::array<T, 2>)[0];
        // ...
    }
}

这方面没什么特别或独特之处。另一种可能性是:

struct B { virtual ~B() {}};
struct D : B {};
struct E : B {};

B *create() {
    if (std::bernoulli_distribution(0.5)(std::default_random_engine())) {
        return new D;
    }
    return new E;
}

或者:

void *create() {
    if (std::bernoulli_distribution(0.5)(std::default_random_engine())) {
        return reinterpret_cast<void*>(new int);
    }
    return reinterpret_cast<void*>(new float);
}

new int[]的唯一区别在于,您无法看到它的实现,看它在要创建的不同类型的对象之间进行选择。

答案 2 :(得分:2)

  

我曾经认为在C ++中所有对象的类型都是在编译时确定的,但上面的例子似乎反驳了这种信念。

您引用的示例是讨论项目的存储持续时间。 C ++识别三个存储持续时间:

  1. 静态存储持续时间是全局和局部静态变量的持续时间。
  2. 自动存储持续时间是“堆栈分配”功能 - 局部变量的持续时间。
  3. 动态存储时长是动态分配内存的持续时间,例如newmalloc
  4. 这里使用“动态”一词与对象的类型无关。它指的是实现必须如何存储构成对象的数据。

      

    我曾经认为在C ++中所有对象的类型都是在编译时确定的,但上面的例子似乎反驳了这种信念。

    在您的示例中,有一个变量,其类型为int*。底层数组没有实际的数组类型,可以以任何有意义的方式恢复到程序。没有动态输入。