我曾经认为这个问题的答案是"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)。[…]
我的问题是:
- 最后一段引用的"properties"是什么意思?显然,一个对象的名称不能算作是"当对象被创建时"确定的东西-除非这里的"创建"的含义与我认为的不同;
- 还有其他对象的类型只在运行时确定的例子吗? 在多大程度上说c++是一种静态类型语言是正确的?或者更确切地说,在这方面对c++进行分类的最合适的方法是什么?
如果有人能详细说明以上几点中的至少一条,那就太好了。
编辑:标准似乎清楚地表明,new
表达式确实创建了一个数组对象,而不是像一些人指出的那样,只是将几个对象布置为一个数组。根据第5.3.4/5段(由Xeo提供):
当分配的对象是数组时(即,使用noptr-new-declarator语法或new-type-id或)type-id表示数组类型),new表达式产生指向数组初始元素(如果有的话)的指针。[注:
new int
和new int[10]
的类型都是int*
,new int[i][10]
的类型是int (*)[10]
。noptr-new-declarator中的属性指定符-seq将指定为关联的数组类型。
new-expression不创建具有运行时变化数组类型的对象。它创建了许多对象,每个对象都是静态类型int
。这些对象的数量是静态未知的。
c++为动态类型提供了两种情况(第5.2.8节):
- 与表达式 的静态类型相同
- 当静态类型为多态时,派生最多的对象的运行时类型
这两个都没有给new int[N]
创建的对象一个动态数组类型。
从学究底上讲,new-expression的求值会创建无限数量的重叠数组对象。从3.8 p2:
[注:数组对象的生命周期从获得合适大小和对齐的存储空间开始,到该数组占用的存储空间被重用或释放时结束。12.6.2描述了基对象和成员子对象的生命周期。
所以如果你想谈论由new int[5]
创建的"数组对象",你不仅要给它类型int[5]
,还要给它类型int[4]
, int[1]
, char[5*sizeof(int)]
和struct s { int x; }[5]
。
我认为这相当于说数组类型在运行时不存在。对象的类型应该是限制性信息,并告诉您有关其属性的一些信息。允许将内存区域视为无限数量的具有不同类型的重叠数组对象,实际上意味着该数组对象是完全无类型的。运行时类型的概念只对存储在数组中的元素对象有意义。
术语"静态类型"one_answers"动态类型"适用于表达式。
静态类型
在不考虑执行语义的情况下对程序进行分析而得到的表达式(3.9)的类型
动态类型& lt; glvalue>由glvalue表达式表示的glvalue所指向的最派生对象的类型(1.8)
此外,您可以看到,动态类型与静态类型只有在静态类型可以派生时才不同,这意味着动态数组类型始终与表达式的静态类型相同。
你的问题是:
但是如何在编译时判断new表达式创建的对象的类型呢?
对象有类型,但它们不是没有指向对象的表达式的"静态"或"动态"类型。给定一个表达式,静态类型在编译时总是已知的。如果没有派生,则动态类型与静态类型相同。
但是你问的是独立于表达式的对象类型。在你给出的例子中,你要求创建一个对象,但你没有指定你想在编译时创建的对象的类型。你可以这样看:
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++中,所有对象的类型都是在编译时确定的,但上面的例子似乎推翻了这种看法。
你举的例子是关于物品的储存时间。c++可以识别三种存储持续时间:
- 静态存储时间是全局静态变量和局部静态变量的持续时间。
- 自动存储持续时间是"堆栈分配"函数局部变量的持续时间。
- 动态存储时长指
new
、malloc
等动态分配内存的时长。
这里"dynamic"一词的使用与对象的类型无关。它指的是实现必须如何存储组成对象的数据。
我过去认为,在c++中,所有对象的类型都是在编译时确定的,但上面的例子似乎推翻了这种看法。
在您的示例中,有一个类型为int*
的变量。底层数组没有一个实际的数组类型,它可以以任何有意义的方式恢复到程序中。这里没有动态输入