【问题标题】:can placement "new" be used to alter "const" data?可以使用放置“新”来更改“常量”数据吗?
【发布时间】:2016-11-11 06:27:06
【问题描述】:

[这是can memcpy() be used to change “const” member data?的后续行动。而Idiomatic Way to declare C++ Immutable Classes 确实解决了这个问题,尤其是this 回答“在围绕不可变数据设计的语言中,它会知道它可以“移动”您的数据,尽管它的(逻辑)不变性。 ]


给定一个structconst 成员

struct point2d { const int x; const int y; }; // can't change to remove "const"

持有指向point2d 的指针的类可以指向具有不同值的新point2d 实例。

struct Bar
{
    std::unique_ptr<point2d> pPt_{ new point2d{ 0, 0 } };
    const point2d& pt() const {
        return *pPt_;
    }

    void move_x(int value) {
        pPt_.reset(new point2d{ pt().x + value, pt().y });
    }
};

Bar 的客户见:

   Bar bar; // (0, 0)
   bar.move_x(3141); // (3141, 0)

point2dBar 都按预期工作;是的,point2d 是完全不可变的。

但是,我真的很想要Bar 的不同实现,它将point2d 实例存储为成员数据。有什么办法可以做到这一点?使用放置new 应该会导致undefined behavior(见评论)。

#include <new>
struct Baz
{
    point2d pt{ 0, 0 };

    void move_x(int value) {
        // ** is this undefined behavior ? **
        new (&pt) point2d { pt.x + value, pt.y };
    }
};

是否直接使用point2d 作为成员数据来解决(潜在?)未定义行为?

struct Blarf
{
    unsigned char pt_[sizeof(point2d)];
    const point2d& pt() const {
        return *reinterpret_cast<const point2d*>(pt_);
    }

    Blarf() {
        new (&pt_) point2d{ 0, 0 };
    }

    void move_x(int value) {
        new (&pt_) point2d{ pt().x + value, pt().y };
    }
};

哪个是正确的?只是Blarf?还是Baz 也可以?或者两者都不是,唯一的解决方案是Bar?

【问题讨论】:

  • 当您链接到的“证明”与placement new 无关时,您为什么说placement new 结果在UB 中?我错过了什么?
  • 哦,cmets,废话。不过,我不确定我是否同意 Sam。放置 new as-if 会破坏原件(如果它可以轻易破坏,则定义明确,例如 int)并用新的东西替换它。请注意,“对象”是内存中的一个区域。但它也有一个类型。两人在安置新人方面存在分歧。我认为关于 would 是否是 UB 的问题会很有趣。我会先问这个问题。
  • @Dan:我想不到,只要你解决了那个数组的潜在对齐问题,我想不出它有什么问题。
  • 顺便说一句,这是一个非常愚蠢的point2d 类;我的哀悼
  • 添加更多混乱 - 如果您显式调用对象的析构函数,然后使用放置 new 重新构建它,如:pt.~point2d(); new (&amp;pt) point2d{x, y};?看起来它实际上可能是一致的,因为原始对象的生命周期已明确结束,并在其位置重建了一个新对象...

标签: c++ constants immutability placement-new


【解决方案1】:

您可以在对象的生命周期结束后重用存储。生命周期以析构函数调用结束。这在技术上没有任何问题。

在对象的生命周期结束后使用对象,正如所提供的示例代码在我写这个答案时

pt.~point2d();
new (&pt) point2d { pt.x + value, pt.y };

是未定义的行为。

如果您坚持使用带有const 字段的点类,您可以像这样解决这个问题:

void move_x( int const value )
{
     auto const old_pt = pt;
     pt.~point2d();
     ::new (&pt) point2d { old_pt.x + value, old_pt.y };
}

这可能让人觉得不必要的复杂性和可能的​​微效率低下,但实际上,不必要的复杂性是点类。

【讨论】:

  • @Dan:估计你的编译器会完全优化掉它。这都是关于形式的,告诉编译器它不能基于仅因为形式 UB 而成立的假设做太聪明的事情。
  • @Dan,虽然这在技术上是对您的特定问题的正确答案,但请特别注意回复的最后一行“这可能让人觉得不必要的复杂性和可能的​​微效率低下,而是不必要的复杂性是重点类。”还有其他更好的方法来处理这个问题。使用这样的“聪明”技巧通常会导致多年后的嘲笑,人们发誓要对你和你所爱的人进行报复。
  • 我猜这个move_x 实现大致就是对容器进行“替换就位”操作。
  • 如果您不能修改该 point2d 类定义的硬性要求,请编写您自己的 point2d 类。尽可能使用该类,并在最后可能的机会转换为垃圾 point2d 类。无论如何,它似乎应该用作“视图”。
  • @Andrew Idiomatic Way to declare C++ Immutable Classes 有很好的解释。
猜你喜欢
  • 2013-10-13
  • 1970-01-01
  • 2014-08-29
  • 1970-01-01
  • 2015-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多