Нашли или выдавили из себя код, который нельзя назвать нормальным,
на который без улыбки не взглянешь?
Не торопитесь его удалять или рефакторить, — запостите его на
говнокод.ру, посмеёмся вместе!
struct A {
virtual int transmogrify();
};
struct B : A {
int transmogrify() override { new(this) A; return 2; }
};
int A::transmogrify() { new(this) B; return 1; }
static_assert(sizeof(B) == sizeof(A));
int main() {
A i;
int n = i.transmogrify();
// int m = i.transmogrify(); // undefined behavior
int m = std::launder(&i)->transmogrify(); // OK
assert(m + n == 3);
}
Yo dawg, we heard you like kostyli, so we put our kostyli into your kostyli, so that you can use kostyli to support our kostyli!
Ну, std::launder не разыменовывает указатель. Он тупо возвращает то, что получил.
Внутри std::launder находится компиляторо-специфический костыль, который можно назвать "optimization barrier", который сигнализирует компилятору "смотри, жывотное, что под указателем, а не в свои сраные кеши". Где-то читал, что в GCC std::launder был реализован просто:
X *p = new X{3, 4};
const int a = p->n;
X* np = new (p) X{5, 6}; // p does not point to new object because X::n is const; np does
const int b = p->n; // undefined behavior
const int c = p->m; // undefined behavior (even though m is non-const, p can't be used)
В первом случае коспилятор мог бы закостылизировать закешировать (на стеке, напр.) значение p->n (ведь X::n -- const int и он не должен менятся, так?). И выражение мог бы p->n заменить на использование кеша. Т.е. вместо ожидаемого 5 в const int b внезапно при выполнении окажется 3.
Во втором случае комитет вероятно пошел еще дальше, и сказал "А что если компилятор закеширует целую структуру под указателем? Ведь мы итак знаем, что итак X::n у нас const и читать его после замены без std::launder -- UB." И дальше все так же как и в предыдущем пункте.
Ввиду некоторой ограниченности языка «PHP» (в частности, невозможности перезаписать $this), точный перевод невозможен. Однако, позволив себе некоторую вольность, этот код можно перевести вот так:
<?
function sizeof_($class)
{
$before = memory_get_usage();
$ret = new $class();
$after = memory_get_usage();
return ($after - $before);
}
function launder($p) {
return $p;
}
class A {
function transmogrify_cpp_translation() {
return 1;
}
function transmogrify() {
return unserialize(preg_replace('/^O:\d+:"[^"]++"/','O:1:"B"', serialize($this)));
}
}
class B extends A {
function transmogrify_cpp_translation() {
return 2;
}
function transmogrify() {
return unserialize(preg_replace('/^O:\d+:"[^"]++"/','O:1:"A"', serialize($this)));
}
}
if (sizeof_("A") != sizeof_("B")) {
echo "error: static assertion failed";
}
$i = new A();
$i = $i->transmogrify();
$n = $i->transmogrify_cpp_translation();
// $i = $i->transmogrify(); // undefined behavior
$i = launder($i)->transmogrify();
$m = $i->transmogrify_cpp_translation();
if ($n + $m != 3) {
echo "assertation \"\$n + \$m != 3\" failed.";
}
?>
http://govnokod.ru/11417
Чисти указатель, животное
Внутри std::launder находится компиляторо-специфический костыль, который можно назвать "optimization barrier", который сигнализирует компилятору "смотри, жывотное, что под указателем, а не в свои сраные кеши". Где-то читал, что в GCC std::launder был реализован просто:
Но потом хлопцы из LLVM/Clang подсуетились и показали who is who, продемонстрировав пример, когда гнутая реализация делала ересь.
Почему тогда здесь убэ, а с launder - не убэ?
Во втором случае комитет вероятно пошел еще дальше, и сказал "А что если компилятор закеширует целую структуру под указателем? Ведь мы итак знаем, что итак X::n у нас const и читать его после замены без std::launder -- UB." И дальше все так же как и в предыдущем пункте.
http://sandbox.onlinephpfunctions.com/code/b7e8dd12f4d3914031e4784fd6768b1f452f3c41