21

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

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

这里的类型foo很明显int[42]。相反,考虑这种情况:

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

这里的类型fooint*,但是如何知道在编译时由表达式创建的对象的类型呢?new(强调是为了强调我不是在谈论表达式返回的指针new,而是谈论由表达式创建的数组对象new)。

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

[...] 由new-expression创建的实体具有动态存储持续时间 (3.7.4)。[注意:此类实体的生命周期不一定限于创建它的范围。—尾注] 如果实体是非数组对象,则new 表达式返回指向所创建对象的指针。如果它是一个数组,则new 表达式 返回一个指向数组初始元素的指针。

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

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

所以我的问题是:

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

如果有人可以至少详细说明上述几点,那就太好了。

编辑:

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

当分配的对象是一个数组(即使用noptr-new-declarator语法或new-type-idtype-id表示数组类型)时,new-expression 产生一个指向初始元素的指针(如果任何)的数组。[注意:两者都new int具有new int[10]类型int*,并且类型new int[i][10]int (*)[10] --end note] noptr-new-declarator中的属性说明符序列属于关联的数组类型

4

3 回答 3

9

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


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

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

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


学究式地,对new-expression的求值会创建无限数量的重叠数组对象。从 3.8p2 开始:

[注意:数组对象的生命周期从获得适当大小和对齐的存储开始,到数组占用的存储被重用或释放时结束。12.6.2 描述了基础子对象和成员子对象的生命周期。——尾注]

因此,如果要谈论由 创建的“数组对象” new int[5],则不仅要给它 type int[5],还要给它int[4], int[1], char[5*sizeof(int)], and struct s { int x; }[5]

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

于 2013-04-10T23:11:58.333 回答
9

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

静态类型

在不考虑执行语义的情况下分析程序产生的表达式(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[]是您无法看到它的实现以看到它在要创建的不同类型的对象之间进行选择。

于 2013-04-11T18:27:21.543 回答
2

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

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

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

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

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

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

于 2013-04-10T23:13:09.527 回答