我曾经认为这个问题的答案是“ 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)。 [...]
所以我的问题是:
如果任何人至少可以详细说明上述一点,那就太棒了。
修改
标准似乎清楚地表明new
表达式确实创建了一个数组对象,而不仅仅是一些像某些人指出的数组一样的对象。根据第5.3.4 / 5段(由Xeo提供):
当分配的对象是数组时(即使用 noptr-new-declarator 语法或 new-type-id 或 type-id 表示数组类型),new-expression产生指向数组初始元素(如果有)的指针。 [注意:
new int
和new int[10]
都有int*
类型,new int[i][10]
类型为int (*)[10]
-end note] noptr-new-declarator 中的 attribute-specifier-seq 将附加到关联的数组类型。
答案 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 ++识别三个存储持续时间:
new
或malloc
。这里使用“动态”一词与对象的类型无关。它指的是实现必须如何存储构成对象的数据。
我曾经认为在C ++中所有对象的类型都是在编译时确定的,但上面的例子似乎反驳了这种信念。
在您的示例中,有一个变量,其类型为int*
。底层数组没有实际的数组类型,可以以任何有意义的方式恢复到程序。没有动态输入。