问题
[ 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:
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.
来源:https://stackoverflow.com/questions/38270827/can-placement-new-be-used-to-alter-const-data