can placement “new” be used to alter “const” data?

2019-02-20 14:58发布

[ This is a follow-up to can memcpy() be used to change “const” member data?. And Idiomatic Way to declare C++ Immutable Classes really gets at the issue, especially this answer "In a language designed around immutable data, it would know it can "move" your data despite its (logical) immutability." ]


Given a struct with const members

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

A class which holds a pointer to point2d can point to a new point2d instance with different values.

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 });
    }
};

Clients of Bar see:

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

Both point2d and Bar are working exactly as desired; yes, point2d is completely immutable.

However, I'd really like a different implementation of Bar that stores the point2d instance as member data. Is there any way to achieve this? Using placement new supposedly results in undefined behavior (see comment).

#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 };
    }
};

Does not using point2d directly as member data work-around the (potential?) undefined behavior?

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 };
    }
};

Which is correct? Just Blarf? Or is Baz also OK? Or neither, and the only solution is Bar?

1条回答
欢心
2楼-- · 2019-02-20 15:14

You can reuse the storage after the lifetime of the object has ended. The lifetime ends with the destructor call. There's nothing technically problematic in that.

Using the object after its lifetime has ended, as the presented example code did at the time I wrote this answer in

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

is Undefined Behavior.

If you insist on using the point class with const fields, you can work around that like this:

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

That may feel like unnecessary complication and possible micro-inefficiency, but rather, the unnecessary complication is the point class.

查看更多
登录 后发表回答