【问题标题】:To what extent is C++ a statically-typed language?C++ 在多大程度上是一种静态类型语言?
【发布时间】:2013-04-03 00:23:21
【问题描述】:

我曾经认为这个问题的答案是“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 表达式创建的 array 对象

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

[...] new-expression 创建的实体具有动态存储持续时间 (3.7.4)。 [注:此类的生命周期 实体不一定限于创建它的范围。 —尾注] 如果实体是非数组 对象,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] —end note ] noptr-new-declarator 中的 attribute-specifier-seq 属于 关联的数组类型

【问题讨论】:

  • 我认为您混淆了dynamicstatic。无论rand() 返回多少索引,您的示例都将返回int*..。那是动态内存分配……不是动态类型。
  • 我几乎可以称之为“类型擦除”
  • 使用 C 风格的数组来探测 C++ 类型系统会导致精神错乱。它们具有从 C 继承而来的相当古怪的属性,不能很好地适应严格的类型系统。所以答案是“那又怎样?”。
  • 字符串文字(在 C++11 之前)也很古怪,除了它们从数组中得到的怪癖之外。字符文字也有一些怪癖。为了理解类型系统,它们是分散注意力的,而不是完全集成的部分。这就是为什么我说“那又怎样”;我觉得它们非常无趣。它们就是它们,它们不适合一个理性的系统。
  • 你知道吗?这是进入 Stroustrup 官方 C++ 常见问题的绝佳候选者。

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


【解决方案1】:

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

静态类型

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


动态类型

由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[] 的唯一区别是您无法查看其实现以查看它在要创建的不同类型的对象之间进行选择。

【讨论】:

  • 同意。此外,维基百科将“静态类型系统”定义为“在编译时执行类型检查,而不是在运行时执行。”,“动态类型系统”定义为“其大部分类型检查在运行时执行,如反对在编译时”。由于 C++ 在运行时进行 no 类型检查,因此这一切仍然是静态的。 (无关,C++ 是弱类型,但安全类型)
  • @MooingDuck:C++ 实际上可以进行一些有限的运行时类型检查,形式为dynamic_cast。如果该类有一个 vtable,并且类型不能静态保证,dynamic_cast 可以并且确实会进行运行时检查,以确保您指向的内容是或继承自您尝试强制转换的类型它到。
【解决方案2】:

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] .

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

【讨论】:

  • 但是数组本身是一个对象,并且有一个包含其大小的类型,不是吗?来自标准:“如果它是一个数组,则 new 表达式返回一个指向数组初始元素的指针。”
  • @sftrabbit:这里没有数组类型的东西。内存中排列了一些对象,但我什至不确定这种排列是否构成一个“数组对象”,即使使用强制转换,您也可以安全地绑定到数组引用变量。
  • @sftrabbit: 如果数组一开始就是一个对象。
  • §5.3.4/5: "当分配的对象为数组(即使用noptr-new-declarator语法或new-type-id 或 type-id 表示数组类型),new-expression 产生一个指向初始元素的指针(如果有) [...] noptr-new-declarator 中的 attribute-specifier-seq 属于 关联的数组类型。 " -- 标准清楚地说正在创建一个数组对象,所以这个答案似乎不对。
  • @Ben:你是说笔记?这表示 表达式 new intnew int[10] 的类型为 int*。这与创建对象的类型无关。
【解决方案3】:

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

您引用的示例是关于项目的存储期限。 C++ 识别三种存储持续时间:

  1. 静态存储持续时间是全局和局部静态变量的持续时间。
  2. 自动存储持续时间是“堆栈分配”函数局部变量的持续时间。
  3. 动态存储持续时间是动态分配内存的持续时间,例如newmalloc

这里使用“动态”这个词与对象的类型无关。它指的是实现必须如何存储构成对象的数据。

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

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

【讨论】:

  • 我知道“ipse dixit”并不是最好的反驳,但这是让我想到这一点的comment by Johannes Schaub。我知道三个存储持续时间以及new 返回int* 的事实。令我困惑的是,是否存在一个“已创建对象”,它具有数组类型,但数组的大小(以及类型)仅在运行时才知道
  • @R.MartinhoFernandes:对不起,我仍然主要在 C++03 领域思考 :)
  • @Andy:C++ 中没有运行时类型的概念。
  • @Billy:当然有:typeid 是主要的“运行时类型信息”工具 (RTTI) 之一。但是类型在编译时也一直存在,在运行时唯一改变的是每种类型存在多少对象以及哪些指针(和引用)与哪些对象相关联。
  • @Billy:这就是我所相信的(从某种意义上说,类型是静态分配给对象的属性)。如果该评论不是来自约翰内斯,我很容易忽略它。
猜你喜欢
  • 1970-01-01
  • 2012-11-04
  • 2012-12-12
  • 2012-12-29
  • 1970-01-01
  • 2021-01-27
  • 2010-09-12
  • 2016-09-26
相关资源
最近更新 更多