问题描述
在下面的简短示例中,指针f
指向的对象或刚好从main
返回之前指向的对象可以怎么说?
#include <vector>
struct foo {
std::vector<int> m;
};
int main()
{
auto f = new foo;
f->~foo();
}
我相信foo
不再指向f
所指向的对象。我收到很多评论,认为这可能不正确,相反,可能有一个对象foo
处于被破坏,无效或无效的状态。
对于存在被明确销毁但其存储仍然有效的对象,语言标准怎么说?
换句话说,可以合理地说f
上还有一个对象不在其生存期内吗?是否存在这样的事物,那就是它的生命周期不是没有开始,没有开始构造并且没有被破坏?
编辑:
很明显,一个对象可以在其生命周期中不存在。在建造和销毁过程中,有一个物体,其寿命尚未开始或已经结束。来自https://timsong-cpp.github.io/cppwp/intro.object#1:
[...]对象在其构造周期([class.cdtor])的整个生命周期以及销毁期间([class.cdtor])占据一个存储区域。 [...]
但是在f->~foo();
指向的对象之后,f
指向的对象(被称为o
)没有被构造,它的生命周期也没有,也没有被破坏。我对本节的理解是,o
不能再占用存储空间,因为它不在任何列举的情况下。似乎这意味着不再有o
,也不再有指向o
的指针。矛盾的是,如果您有一个指向o
的指针,则该指针将指向o
无法占据的存储空间。
编辑2:
如果没有对象了,那么foo
有什么样的价值?似乎唯一可能的合理值是指向对象的指针,这将与该语句相矛盾。参见this question。
解决方法
在C ++中,对象本质上是永恒的。语言中没有什么可以使对象消失的。处于生存期之外的对象仍然是一个对象,仍在占用存储空间,并且该标准具有specific things,您可以使用指向超出其生存期的对象的指针/引用。
仅当不可能有有效的指针/引用时,该对象才真正消失。当该对象占用的存储空间结束其存储持续时间时,就会发生这种情况。指向超出其持续时间的存储的指针是无效的指针,即使该地址本身以后再次变得有效,也是如此。
因此,通过调用析构函数而不是使用delete f
(这也会取消分配存储空间),f
仍指向类型为foo
的对象,但是该对象位于其对象之外一生。
我上面陈述的理由基本上可以归结为对支持未创建对象概念所需的规定完全没有规定的标准。
对象创建在哪里?
该标准提供明确,明确的声明,说明对象何时会出现在一块存储中。 [intro.object]/1概述了引发对象创建的确切机制。
该标准提供了有关对象生命周期何时开始和结束的明确,明确的声明。 [basic.life]完全概述了这些内容,但是[basic.life] / 1特别说明了对象的生存期何时开始和结束。
标准不不提供有关对象何时不存在的任何陈述(明确或其他方式)。该标准规定了对象的创建时间,生命周期的开始时间以及结束时间。但是从不会说它们何时停止存在于某个存储中。
还讨论了以下形式的声明:
可以使用任何表示对象将要存储或 所位于的存储位置的地址的指针 ,但只能以有限的方式使用。
添加了重点。
使用过去时表示对象不再位于该存储中。但是什么时候该物体停止停在那里?没有 clear 声明是什么原因导致的。除此之外,在这里使用过去时就没关系了。
如果您不能指出何时停止在那里的声明,那么您绝对可以说的是,标准中有几个地方可以清除措辞。并不能消除标准没有说明何时对象停止存在的明确事实。
指针有效性
但是它确实说明了何时无法再访问对象。
为了使某个对象停止存在,该标准将必须考虑在这些对象不再存在时指向这些对象的指针。毕竟,如果指针指向一个对象,那么该对象必须仍然存在,对吧?
[basic.compound]/3概述了指针可以具有的状态。指针可以处于以下四种状态之一:
- 指向对象或函数的指针(据说该指针指向该对象或函数),或者
- 超出对象末尾的指针([expr.add]),或
- 该类型的空指针值([conv.ptr]),或
- 无效的指针值。
没有指向没有对象的指针的余量。允许使用“无效的指针值”,但是指针仅在the storage duration for the storage they point into ends时变为无效:
到达存储区域的持续时间结束时,表示该存储区域任何部分地址的所有指针的值将变为无效的指针值。
请注意,该语句意味着所有指向此类对象的指针都不再处于“对象指针”状态,而进入“无效指针”状态。因此,此类存储中的对象(包括生命周期内外的对象)都将无法访问。
这正是标准要支持不再存在的对象的概念所需要的语句。
但是没有这样的陈述。
[basic.life]确实有一些语句,这些语句解决了可以使用指向超出其生命周期的对象的指针的有限方式。但请注意其使用的特定措辞:
有关正在构造或破坏的对象,请参见[class.cdtor]。否则,此类指针将引用已分配的存储([basic.stc.dynamic.deallocation]),并且使用该指针时,就像该指针的类型为void *一样,都是定义良好的。
从不表示指针“指向”已分配的存储。它永远不会撤消[basic.compound] / 3关于指针种类的声明。指针仍然是指向对象的指针。只是指针“指向已分配的存储”。并且该指针可以用作void*
。
也就是说,没有“分配存储的指针”之类的东西。存在“指向对象生命周期之外的指针,其指针值可用于引用分配的存储”。但是仍然是“指向对象的指针”。
生命不存在
对象必须存在才能具有生命周期。该标准明确规定了这一点。但是,该标准在任何时候都没有将对象的存在与其生存期联系起来。
实际上,如果结束对象的生存期意味着该对象不存在,则对象模型的复杂程度将大大降低。 [basic.life]的大多数内容都是关于在对象的生存期之外,使用特定的方法来使用对象的名称或指向该对象的指针/引用。如果对象本身不存在,我们就不需要那种东西。
有关此事的讨论中提到的是:
我相信提及寿命终止对象是为了说明正在构造的对象和正在破坏的对象。
如果是这样,[basic.life]/8 talking about with this statement是什么:
如果在一个对象的生命周期结束之后并且在重新使用或释放该对象所占用的存储之前,在原始对象所占用的存储位置上创建了一个新对象,该对象指向原始对象,引用原始对象的引用或原始对象的名称
如果在对象的生命周期结束时指向原始对象的指针变为指向已分配内存的指针,为什么此语句谈论指向原始对象的指针?指针不能指向不存在的对象,因为它们不存在。
仅当那些对象在其生命周期之外继续存在时,此段落才有意义。不,它不仅在构造函数/析构函数中;本节中的示例清楚地表明了这一点:
struct C {
int i;
void f();
const C& operator=( const C& );
};
const C& C::operator=( const C& other) {
if ( this != &other ) {
this->~C(); // lifetime of *this ends
new (this) C(other); // new object of type C created
f(); // well-defined
}
return *this;
}
C c1;
C c2;
c1 = c2; // well-defined
c1.f(); // well-defined; c1 refers to a new object of type C
在operator=
确实调用了析构函数的同时,析构函数在使用this
指针之前完成了 。因此,[class.cdtor]的特殊规定在创建新对象时不适用于this
。因此,新对象是在对旧对象的析构函数调用的外部中创建的。
因此,很明显,对象的“生命周期之外”规则旨在始终有效。这不仅仅是构造函数/析构函数的规定(如果有的话,它将明确地指出这一点)。这意味着在创建新对象之前,名称/指针/引用仍必须在其生命周期之外命名/指向/引用对象。
要做到这一点,它们命名/指向/引用的对象仍然必须存在。