允许我在开始发言时说,出于明显的原因,我不建议您采用以下任何一种做法。但是,我今天对此进行了讨论,有些人坚持使用这样的引用作为未定义的行为。
这是一个测试用例:
#include <string>
struct my_object {
int a = 1;
int b = 2;
std::string hi = "hello";
};
// Using union purely to reserve uninitialized memory for a class.
union my_object_storage {
char dummy;
my_object memory;
// C++ will yell at you for doing this without some constructors.
my_object_storage() {}
~my_object_storage() {}
} my_object_storage_instance;
// This is so we can easily access the storage memory through "I"
constexpr my_object &I = my_object_storage_instance.memory;
//-------------------------------------------------------------
int main() {
// Initialize the object.
new (&I) my_object();
// Use the reference.
I.a = 1;
// Destroy the object (typically this should be done using RAII).
I.~my_object();
// Phase two, REINITIALIZE an object with the SAME reference.
// We still have the memory allocated which is static, so why not?
new (&I) my_object();
// Use the reference.
I.a = 1;
// Destroy the object again.
I.~my_object();
}
https://wandbox.org/permlink/YEp9aQUcWdA9YiBI
基本上,该代码将为结构保留静态内存,然后在main()中对其进行初始化。你为什么想这么做?它不是非常有用,您应该只使用一个指针,但这是一个问题:
给出此声明
constexpr my_object &I = my_object_storage_instance.memory;
是否正在定义对未初始化的内存未定义行为的引用?其他人告诉过我,但我正在尝试具体弄清是否是这种情况。在C ++标准中,我们看到以下段落:
应将引用初始化为引用有效的对象或函数。 [注意:特别是,空引用不能存在于定义良好的程序中,因为创建此类引用的唯一方法是将其绑定到通过解引用空指针而获得的“对象”,这会导致未定义的行为。 / p>
特别是“有效对象” ,可能会归结为:是一个尚未将其构造函数称为“有效”的对象吗?是什么使它会导致不确定的行为呢?真的会出现真正的副作用吗?
我的论据用于,被标记为未定义的行为是:
我的论据反对这是未定义的行为,是:
nullptr
是未定义的。如果没有任何取消引用,它没有具体说明未定义的行为。同样,在实践中并不是很有用,因为有更好的方法来度过您的时间,但是比stackoverflow哪个地方更适合提出奇怪的问题和专家意见? :)
答案 0 :(得分:6)
很好,您对引用的使用属于要求有活动对象的规则的明确例外。在[basic.life]中:
类似地,在对象的生存期开始之前但已分配了该对象将占用的存储空间之后,或者在对象的生存期结束之后以及重新使用或释放该对象占用的存储空间之前,所有glvalue可以使用指向原始对象的符号,但只能以有限的方式使用。
有关正在构造或破坏的对象,请参见[class.cdtor]。否则,此类glvalue会引用已分配的存储([basic.stc.dynamic.allocation]),并且使用不依赖于glvalue的值的属性是明确定义的。该程序在以下情况下具有未定义的行为:
- glvalue用于访问对象,或
- glvalue用于调用对象的非静态成员函数,或者
- glvalue绑定到对虚拟基类([dcl.init.ref])的引用,或者
- glvalue用作
dynamic_cast
([expr.dynamic.cast])的操作数或typeid
的操作数。如果在对象的生存期结束之后且在重新使用或释放该对象所占用的存储之前,在原始对象所占用的存储位置上创建了一个指向原始对象的指针的新对象,引用原始对象的引用或原始对象的名称将自动引用新对象,并且一旦开始新对象的生存期,就可以使用它来操作新对象,如果:
- 新对象的存储空间正好覆盖了原始对象所占据的存储位置,并且
- 新对象的类型与原始对象相同(忽略顶级cv限定词),并且
- 原始对象的类型不是const限定的,并且,如果是类类型,则不包含任何类型为const限定的非静态数据成员或引用类型,并且
- 原始对象和新对象都不是潜在重叠的子对象([intro.object])。
因此,您的引用有效地指向分配的存储,这正是执行新放置并激活工会成员所需要的。
由于您创建的对象的动态(运行时)类型与您持有的引用的静态类型完全匹配,因此可以在放置新对象(第一个或第二个)之后使用它来访问新对象。