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

And*_*owl 21 c++ static-typing dynamic-typing language-lawyer c++11

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

int main()
{
    int foo[42] = { 0 };
}
Run Code Online (Sandbox Code Playgroud)

这里的类型foo很明显int[42].相反,请考虑以下情况:

int main()
{
    int* foo = new int[rand() % 42];
    delete[] foo;
}
Run Code Online (Sandbox Code Playgroud)

这里foo是is 的类型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. 最后引用的段落中的" 属性 " 是什么意思?显然,对象的名称不能算作" 在创建对象时"确定的东西- 除非" 创建 "在这里意味着与我想的不同;
  2. 是否有其他类型仅在运行时确定类型的对象示例?
  3. 在多大程度上说C++是一种静态类型的语言是正确的?或者更确切地说,在这方面对C++进行分类的最恰当方式是什么?

如果有人能够至少详细说明上述一点,那就太好了.

编辑:

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

当分配的对象是数组时(即,使用noptr-new-declarator语法或new-type-idtype-id表示数组类型),new-expression产生指向初始元素的指针(如果任何)数组.[注:既new intnew int[10]具有类型int*和类型new int[i][10]int (*)[10] 末端音符]的属性说明符-SEQnoptr新声明符 appertains 到相关联的阵列型.

Ben*_*igt 9

新的表达不会产生与运行时间变化的阵列类型的对象.它创建了许多对象,每个对象都是静态类型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].

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

  • `§5.3.4/ 5`:"当分配的**对象是数组**时(即使用*noptr-new-declarator*语法或*new-type-id*或*type-id*表示数组类型),*new-expression*产生指向数组初始元素(如果有)的指针.*noptr-new-declarator*中的*attribute-specifier-seq*appertains**到相关的数组类型**." - 标准*明确*表示正在创建一个数组对象,所以这个答案似乎不正确. (6认同)
  • @sftrabbit:这里没有任何数据类型.内存中有对象排列,但我甚至不确定这种排列构成了一个"数组对象",即使使用强制转换,也可以安全地绑定到引用数组变量. (3认同)
  • @Ben:你的意思是笔记?这就是说*表达式``new int`和`new int [10]`的类型为`int*`.这与创建的对象的类型无关. (3认同)

bam*_*s53 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];
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

关于这一点没什么特别或独特的.另一种可能性是:

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;
}
Run Code Online (Sandbox Code Playgroud)

要么:

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

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

  • @MooingDuck:C++实际上可以以`dynamic_cast`的形式进行一些有限的运行时类型检查.如果类有一个vtable,并且类型不能被静态保证,`dynamic_cast`可以并且确实进行运行时检查以确保你指向的东西是或者继承你正在尝试强制转换的类型它来. (2认同)