【问题标题】:Placement new breaks consts and references?放置新刹车的成本和参考?
【发布时间】:2011-11-27 01:28:53
【问题描述】:

my answerthis question 的讨论之后,显然:

以下代码允许的

struct Foo {
    int x;
};

Foo f;
Foo & f_ref = f;

(&f) -> ~Foo ();
new (&f) Foo ();

int x = f_ref .x;

但以下代码不允许

struct Foo {
    const int & x;           // difference is const reference
    Foo (int & i) : x(i) {}
};

int i;
Foo f (i);
Foo & f_ref = f;

(&f) -> ~Foo ();
new (&f) Foo (i);

int x = f_ref .x;

因为$3.8/7

如果在对象的生命周期结束之后,在对象占用的存储空间被重用或释放之前,在原始对象占用的存储位置创建一个新对象,一个指向原始对象的指针,引用原始对象的引用或原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,可用于操作新对象,如果:

  • 原始对象的类型不是 const 限定的,并且,如果是类类型,不包含任何类型为 const 限定或引用类型的非静态数据成员 .. .

我可以理解当 f 不再存在时对 f.x 的引用如何失效,但我不明白为什么 f_ref 应该被失效纯粹因为它的成员之一是 const 和/或引用而不是其他: 之前是对Foo 的引用,之后是对Foo 的引用。

有人能解释一下这种情况背后的原因吗?

编辑

感谢您的回答。我不赞成“保证它不会改变”的说法,因为我们不允许目前允许优化器缓存引用,例如:

struct Foo {
    const int & x;
    Foo (const int & i) : x(i) {}
    void do_it ();
};

int i;
Foo f (i);
const int & ii = f.x;

f .do_it (); // may modify i
std :: cout << ii; // May NOT use cached i

我看不出do_it 是如何允许使引用值无效的,但operator new 不允许——Sequence points invalidate cached values:为什么要豁免 delete/placement-new?

【问题讨论】:

  • 没关系,OP编辑了代码。
  • 我不明白,do_it 怎么能修改i
  • @uncleBens: 例如do_it 中的const_cast&lt;int &amp;&gt;(x) = 1; 是完全可以的,因为x 的引用实际上是一个可变的int 对象。如果这就是 do_it 所做的,那么执行 const int i; Foo f(i); f.do_it(); 将是一个错误 (UB),但使用非 const i 就可以了。

标签: c++ reference constants c++11 placement-new


【解决方案1】:

我相信其动机是允许编译器缓存const 对象的值(注意,这是 const objects,而不仅仅是指向 const 的指针和指向 const 的引用) ,以及引用的引用地址,跨对未知代码的调用。

在您的第二个示例中,编译器首先可以“看到”该对象已被创建和销毁,其次它是使用相同的值重新创建的。但该标准的作者希望允许编译器转换此代码:

struct Foo {
    const int & x;
    Foo (int & i) : x(i) {}
};

int i = 1;
Foo f(i);

some_function_in_another_TU(&f);

std::cout << f.x;

进入这个:

struct Foo {
    const int & x;
    Foo (int & i) : x(i) {}
};

int i = 1;
Foo f(i);

some_function_in_another_TU(&f);

std::cout << i;           // this line is optimized

因为f 的引用成员不能被重新安装,因此必须仍然引用i。 destruct-and-construct 操作违反了引用成员 x 的 non-reseatable-ness。

这种优化应该不会引起特别大的争议:考虑以下示例,使用const 对象而不是具有const 或引用成员的对象:

const int i = 1;
some_function_in_another_TU(&i);
std::cout << i;

这里i 是一个编译时常量,some_function_in_another_TU 不能有效地销毁它并在其位置创建另一个具有不同值的int。因此,应该允许编译器为std::cout &lt;&lt; 1; 发出代码,这个想法是,对于其他类型的 const 对象和引用,类似地应该也是如此。

如果对未知代码的调用可以重新定位引用成员,或更改 const 数据成员的值,那么语言的有用不变量(引用永远不会重新定位,const 对象永远不会改变它们的值)将被破坏.

【讨论】:

  • 您能否对问题底部的编辑发表评论?谢谢。
  • @spraff:您没有区分缓存引用的值和缓存引用的位置。我的std::cout &lt;&lt; i 示例是后者。在我看来,您的“可能不使用缓存的 i”示例是在说不能缓存 i 的值,这是真的。但是ii的引用位置是已知的,可以缓存,只有值可以改变。你的例子与我所看到的并不真正相关。
  • "我的例子是 std::cout std::cout << i; 的例子。在该评论中,我指的是我的示例,其中std::cout &lt;&lt; f.x; 可以由编译器有效地转换为std::cout &lt;&lt; i;
【解决方案2】:

据我所知,这只是语义正确性的问题,以及优化器可能做出的假设。考虑一下:

Bar important, relevant;

Foo x(important);  // binds as const-reference

Zoo z(x);  // also binds as const reference

do_stuff(z);

x.~Foo();
::new (&x) Foo(relevant);  // Ouch?

对象z 可以合理地期望它的Foo 成员引用是常量,因此引用important。正如标准所说,最后两行中的破坏加上新构造“自动更新所有引用以引用(逻辑上)新对象”,所以现在z 中的 const-reference 已经改变,尽管承诺保持不变.

为了避免这种对 const 正确性的背叛,整个就地重建是被禁止的。

【讨论】:

  • +1 表示术语 backstabbing violation。这是背刺违规的完美例子。
【解决方案3】:

优化。假设我有:

struct Foo
{
    int const x;
    Foo( int init ) : x( init ) {}
};

int
main()
{
    Foo a( 42 );
    std::cout << a.x << std::endl;
    new (&a) Foo( 3 );
    std::cout << a.x << std::endl;
    return 0;
}

编译器在看到const int 对象后,有权假设 值不变;优化器可能会简单地保留该值 在新位置的寄存器中,然后再次输出。

请注意,您的示例实际上完全不同。数据成员有 输入int const&amp;;它是一个引用(并且引用始终是 const), 所以编译器可以假设引用总是指向同一个 目的。 (这个对象的值可能会改变,除非对象本身 也是 const。)编译器 然而,不能对它所指的对象的值做出这样的假设,因为i (在你的情况下)可以明显改变。这是事实,参考 本身(与所有引用一样)是不可变的,导致此处出现未定义的行为,而不是 const 你写的。

【讨论】:

  • “它所指的内容可能不会改变” - 我同意你的说法,但我的经验是这样的陈述容易被误解。它引用的对象的值可以改变,但它引用的对象不能改变。有些人会读到“它所指的东西不能改变”,并认为你(错误地)声称它所指的对象(的值)不能改变,而实际上你是(正确地)说它前后还是指同一个对象。
  • @Steve Jessop 我的措辞似乎很不幸,因为它很容易以两种不同的方式理解。我会更正它,以便它(希望)不那么模棱两可。
【解决方案4】:

如果某些东西是 const 限定的,那么你不应该修改它。延长其使用寿命是一种具有严重后果的修改。 (例如,考虑析构函数是否有副作用。)

【讨论】:

  • 如果 x 是对非 const y 的 const 引用,x 仍然可以更改,因为 y 可以更改 - 限制是您不能 使用 x 作为修改y 的手段。
  • 是的,完全正确。你是否认为这与我所说的有些不同? (如果 your 与某事物的唯一连接是对它的 const 引用,则 you 不应修改它。在这种情况下,const 引用是唯一的连接。)
  • 同意。这并不排除else改变它,这就是整个业务的真正意义。
  • 并非如此,因为“其他东西”仅通过 const 引用连接到该对象。只有这样,它不应该被允许改变它的生命周期。也就是说,您拥有对 Y 具有 const-reference 的 X。因此不应允许您更改 Y 的生命周期。因此,您可能不会延长 X 的生命周期,因为这会改变 Y 的生命周期。
猜你喜欢
  • 2018-04-19
  • 1970-01-01
  • 1970-01-01
  • 2018-11-13
  • 2017-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多