Re[4]: С++, как вызвать конструктор объекта по месту?
От: c-smile Канада http://terrainformatica.com
Дата: 21.01.14 17:05
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, c-smile, Вы писали:


CS>>Контекст проблемы: http://stackoverflow.com/questions/21212379/changing-vtbl-of-existing-object-on-the-fly-dynamic-subclassing


К>А если не выклёвывать себе мозг всякими трюками, и сделать по-честному:


"По честному" это писать VTBL руками. С сигнатурами функций типа doIt(Data* _this, ) и пр.
В итоге получишь то что компилятор C++ и так делает, как ты сам сказал когда-то.

Вот про это

Base t(std::move(b));
Derived* d = new(&b)Derived(std::move(t));


я думал. Попробуй в members описать std::string поле.

Короче вместо Base(NO_INIT) конструкторов тебе нужно будет писать Base(Base &&moved) конструкторы. Ровно столько же.
И это всегда будет требовать temporary при конвертации и ненулевой CPU penalty.
Re[6]: С++, как вызвать конструктор объекта по месту?
От: c-smile Канада http://terrainformatica.com
Дата: 21.01.14 17:35
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Здравствуйте, c-smile, Вы писали:


CS>>>>Вызвать placement new без вызова конструктора.


J>>>placement new без вызова конструктора называется reinterpret_cast


CS>>Ну нет конечно. placement new как минимум инициализирует vtbl если она есть. reinterpret_cast он ничего не делает. Или я не понял сермяжность.


J>Это делает конструктор. Вернее, стандарт не специфицирует vtbl вообще, но по сути этим занимается конструктор в качестве первого шага, так как указатель на vtbl — это просто скрытый член, который надо проинициализировать.


Это происходит между new и первым конструктором. Но это не суть важно.

J>Так что сермяжность очень простая: new выделяет память, вызывает конструктор, возвращает указатель нужного типа.

J>placement new убирает первый шаг, оставляя вызов конструктора и возврат указателя нужного типа.
J>Если ты убираешь и конструктор, остается просто возврат указателя — это reinterpret_cast.

Ну ок. "Рука бога" вставляет vtbl pointer.
Для простоты будем считать что кроме VTBL никакой другой практической имплементации виртуальности нам не известно.
Во всяком случае для всех компиляторов поддерживающих COM и CORBA объекты из коробки это именно VTBL и ничто другое.

J>>>Для произвольного левого класса это невозможно сделать, если ты хочешь, чтоб код оставался соответствующим Стандарту. Если классы все под твоим контролем — юзай указатель pimpl, в него ты можешь засунуть все, что угодно (в т.ч. std::string), и не инициализировать, если он уже есть (ну или передавать указатель в конструктор, чтоб уж совсем не зависеть от его точного расположения в классе).


Ищется решение которое вводит наиментшее кол-во сущностей и CPU penalty.

J>Но если ты хочешь пользоваться встроенной системой типов С++ в соответствии со стандартом — придется соответствовать, не предполагая, в частности, никаких vtbl и прочего.


Странно, все говорят про Стандарт, но никто так конкретно и не говорит чем мое решение его нарушает.

Вообще какая-то странная ситуация получается. С одной стороны использование reinterpret_cast конструкций типа как легитимно в C++.
А с другой стороны вариация оного так сразу супротив Стандарта.
Re[7]: С++, как вызвать конструктор объекта по месту?
От: cserg  
Дата: 21.01.14 18:22
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Ищется решение которое вводит наиментшее кол-во сущностей и CPU penalty.


Ну давайте, тогда объясняйте чем решение через aligned_storage не подходит.
Re[5]: С++, как вызвать конструктор объекта по месту?
От: Кодт Россия  
Дата: 21.01.14 19:41
Оценка: 42 (1) +1
Здравствуйте, c-smile, Вы писали:

К>>А если не выклёвывать себе мозг всякими трюками, и сделать по-честному:

CS>"По честному" это писать VTBL руками. С сигнатурами функций типа doIt(Data* _this, ) и пр.
CS>В итоге получишь то что компилятор C++ и так делает, как ты сам сказал когда-то.

А ты бы лучше рассказал, зачем тебе все эти танцы с бубном.
Возможно, что у надзадачи есть изящные решения, не связанные с тем, как обхитрить ООП.


CS>Вот про это


CS>
CS>Base t(std::move(b));
CS>Derived* d = new(&b)Derived(std::move(t));
CS>


CS>я думал. Попробуй в members описать std::string поле.


Попробовал, у меня всё заработало.
  Скрытый текст
#include <iostream>
#include <string>
#include <memory>

struct A
{
    int x;
    std::string y;
    A(int x, std::string y) : x(x), y(y) {}

    virtual const char* who() const { return "A"; }
    void show() const { std::cout << (void const*)this << " " << who() << " " << x << " [" << y << "]" << std::endl; }
};

struct B : A
{
    virtual const char* who() const { return "B"; }
    B(A&& a) : A(std::move(a)) {}
};

int main()
{
    A a(123, "mama");
    a.show(); // 0xbfbefa58 A 123 [mama]

    A t(std::move(a));
    a.show(); // 0xbfbefa58 A 123 []
    t.show(); // 0xbfbefa64 A 123 [mama]
    new(&a)B(std::move(t));
    a.show(); // 0xbfbefa58 B 123 [mama]
    t.show(); // 0xbfbefa64 A 123 []
}


CS>Короче вместо Base(NO_INIT) конструкторов тебе нужно будет писать Base(Base &&moved) конструкторы. Ровно столько же.

CS>И это всегда будет требовать temporary при конвертации и ненулевой CPU penalty.

Это, по крайней мере, — в рамках языка. Хоть и UB.
А если добавить перед new(&a) вызов a.~A() — так и вообще станет похоже на использование aligned_storage.
Хакать же все vfptr'ы — непереносимое решение, требующее знания лэяута конкретных классов на конкретной платформе.

Нулевой штраф производительности — это паттерны Стратегия и Состояние.
На NVI там даже лишней косвенности не возникнет, если тебя волнует штраф производительности при дальнейшей работе...
Перекуём баги на фичи!
Re[7]: С++, как вызвать конструктор объекта по месту?
От: Кодт Россия  
Дата: 21.01.14 19:53
Оценка:
Здравствуйте, c-smile, Вы писали:

J>>Если ты убираешь и конструктор, остается просто возврат указателя — это reinterpret_cast.

CS>Ну ок. "Рука бога" вставляет vtbl pointer.

Вот чтобы специально расстроить, напомню: в классе может быть много vfptr'ов.
У первой полиморфной базы — с нулевым смещением; у последующих полиморфных баз; ну и у членов-данных, но они при подмене типа не поменяются.

CS>Для простоты будем считать что кроме VTBL никакой другой практической имплементации виртуальности нам не известно.

CS>Во всяком случае для всех компиляторов поддерживающих COM и CORBA объекты из коробки это именно VTBL и ничто другое.

NVI — рукодельщина из коробки.
Если хорошенько шаблонно поколдовать, то можем и автоматизировать её.

Сразу вспоминаются shared_ptr, function и any.

J>>Но если ты хочешь пользоваться встроенной системой типов С++ в соответствии со стандартом — придется соответствовать, не предполагая, в частности, никаких vtbl и прочего.

CS>Странно, все говорят про Стандарт, но никто так конкретно и не говорит чем мое решение его нарушает.

Ну как что нарушает. Вагон неопределённого поведения.

Хирургия с подменой vfptr — это частный случай стрельбы по памяти.

placement new — инвалидирует старый объект, ну и что, что почти такой же новый по тому же адресу возник. Как мы видели, компилятор может надеяться на то, что если объект валидный, то и его тип неизменный и известный заранее, и сэкономить на виртуальных вызовах.

CS>Вообще какая-то странная ситуация получается. С одной стороны использование reinterpret_cast конструкций типа как легитимно в C++.

CS>А с другой стороны вариация оного так сразу супротив Стандарта.

Стандарт разрешает стрелять в ногу, но говорит, что скорую помощь и патологоанатома будут оплачивать жертва и её родственники.
Перекуём баги на фичи!
Re[8]: С++, как вызвать конструктор объекта по месту?
От: c-smile Канада http://terrainformatica.com
Дата: 21.01.14 21:31
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, c-smile, Вы писали:


J>>>Если ты убираешь и конструктор, остается просто возврат указателя — это reinterpret_cast.

CS>>Ну ок. "Рука бога" вставляет vtbl pointer.

К>Вот чтобы специально расстроить, напомню: в классе может быть много vfptr'ов.

К>У первой полиморфной базы — с нулевым смещением; у последующих полиморфных баз; ну и у членов-данных, но они при подмене типа не поменяются.

Нет там полиморфных баз по условию задачи. Еще раз: рассматривается конкретная задача а не конь в ваакуме.

J>>>Но если ты хочешь пользоваться встроенной системой типов С++ в соответствии со стандартом — придется соответствовать, не предполагая, в частности, никаких vtbl и прочего.

CS>>Странно, все говорят про Стандарт, но никто так конкретно и не говорит чем мое решение его нарушает.

К>Ну как что нарушает. Вагон неопределённого поведения.


"Имя, сестра, имя!" Что конкретно неопределено?

new(place), использование конструкторов или что?

К>Хирургия с подменой vfptr — это частный случай стрельбы по памяти.


Где я там меняю vfptr?

К>placement new — инвалидирует старый объект, ну и что, что почти такой же новый по тому же адресу возник. Как мы видели, компилятор может надеяться на то, что если объект валидный, то и его тип неизменный и известный заранее, и сэкономить на виртуальных вызовах.


Не может он там экономить. Обращение всегда идет через pointer на базу (Thing).

CS>>Вообще какая-то странная ситуация получается. С одной стороны использование reinterpret_cast конструкций типа как легитимно в C++.

CS>>А с другой стороны вариация оного так сразу супротив Стандарта.

К>Стандарт разрешает стрелять в ногу, но говорит, что скорую помощь и патологоанатома будут оплачивать жертва и её родственники.


Николай, давай на технических деталях сфокусируемся, лирику люди поют в политике.
Re[9]: С++, как вызвать конструктор объекта по месту?
От: niXman Ниоткуда https://github.com/niXman
Дата: 21.01.14 21:36
Оценка:
Здравствуйте, c-smile, Вы писали:

К>>placement new — инвалидирует старый объект, ну и что, что почти такой же новый по тому же адресу возник. Как мы видели, компилятор может надеяться на то, что если объект валидный, то и его тип неизменный и известный заранее, и сэкономить на виртуальных вызовах.


CS>Не может он там экономить.


ошибаетесь. GCC и Clang успешно справляются с двевертуализацией там, где она не нужна.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[6]: С++, как вызвать конструктор объекта по месту?
От: c-smile Канада http://terrainformatica.com
Дата: 21.01.14 21:38
Оценка:
Здравствуйте, Кодт, Вы писали:

CS>>Короче вместо Base(NO_INIT) конструкторов тебе нужно будет писать Base(Base &&moved) конструкторы. Ровно столько же.

CS>>И это всегда будет требовать temporary при конвертации и ненулевой CPU penalty.

К>Это, по крайней мере, — в рамках языка. Хоть и UB.

К>А если добавить перед new(&a) вызов a.~A() — так и вообще станет похоже на использование aligned_storage.

В чем там UB?

К>Хакать же все vfptr'ы — непереносимое решение, требующее знания лэяута конкретных классов на конкретной платформе.


Да не хакаю я vfptr'ы там нигде! В том то и point. По месту одного объекта создается другой. В чем проблема-то?

К>Нулевой штраф производительности — это паттерны Стратегия и Состояние.

К>На NVI там даже лишней косвенности не возникнет, если тебя волнует штраф производительности при дальнейшей работе...
Re[6]: С++, как вызвать конструктор объекта по месту?
От: c-smile Канада http://terrainformatica.com
Дата: 21.01.14 22:31
Оценка:
Здравствуйте, Кодт, Вы писали:

Вот твоя идея, но немного доработанная: http://ideone.com/5O6SFV
Re[10]: С++, как вызвать конструктор объекта по месту?
От: c-smile Канада http://terrainformatica.com
Дата: 22.01.14 00:16
Оценка:
Здравствуйте, niXman, Вы писали:

X>Здравствуйте, c-smile, Вы писали:


К>>>placement new — инвалидирует старый объект, ну и что, что почти такой же новый по тому же адресу возник. Как мы видели, компилятор может надеяться на то, что если объект валидный, то и его тип неизменный и известный заранее, и сэкономить на виртуальных вызовах.


CS>>Не может он там экономить.


X>ошибаетесь. GCC и Clang успешно справляются с двевертуализацией там, где она не нужна.


Если обращаться derived.virtualMethod() то да, может.
Но base->virtualMethod() — это совсем другая песня.
Re[11]: С++, как вызвать конструктор объекта по месту?
От: k.o. Россия  
Дата: 22.01.14 07:34
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Здравствуйте, niXman, Вы писали:


X>>Здравствуйте, c-smile, Вы писали:


К>>>>placement new — инвалидирует старый объект, ну и что, что почти такой же новый по тому же адресу возник. Как мы видели, компилятор может надеяться на то, что если объект валидный, то и его тип неизменный и известный заранее, и сэкономить на виртуальных вызовах.


CS>>>Не может он там экономить.


почему нет?

X>>ошибаетесь. GCC и Clang успешно справляются с двевертуализацией там, где она не нужна.


CS>Если обращаться derived.virtualMethod() то да, может.

CS>Но base->virtualMethod() — это совсем другая песня.

Другая будет если менять base после создания нового объекта

base = turn_A_to<B>(base);

иначе будет UB, компилятор имеет право считать, что vfptr не поменялся и вызывать методы для старого типа.
Re[11]: С++, как вызвать конструктор объекта по месту?
От: Tilir Россия http://tilir.livejournal.com
Дата: 22.01.14 08:12
Оценка: -1
Здравствуйте, c-smile, Вы писали:

CS>Если обращаться derived.virtualMethod() то да, может.

CS>Но base->virtualMethod() — это совсем другая песня.

То не подумать ли ещё раз над другой песней?

#include <iostream>

struct A {
    virtual void func (void) {std::cout << "A\n";};
};

struct B : A {
    virtual void func (void) {std::cout << "B\n";}
};

static int
bar (B *b)
{
  b->func(); //this will be inlined in optimized builds.
}

int
main (void)
{
  B b;
  bar (&b);
  return 0;
}


Компилируем gcc 4.8.1 c -O2, смотрим на сгенерированный код

main:
subq $24, %rsp
movq %rsp, %rdi
movq $_ZTV1B+16, (%rsp)
call _ZN1B4funcEv
... etc

_ZN1B4funcEv:
movl $2, %edx
movl $.LC0, %esi
movl $_ZSt4cout, %edi
jmp _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
... etc

.LC0:
.string "B\n"

Re[12]: С++, как вызвать конструктор объекта по месту?
От: k.o. Россия  
Дата: 22.01.14 08:57
Оценка:
Здравствуйте, Tilir, Вы писали:

T>Здравствуйте, c-smile, Вы писали:


CS>>Если обращаться derived.virtualMethod() то да, может.

CS>>Но base->virtualMethod() — это совсем другая песня.

T>То не подумать ли ещё раз над другой песней?


T>Компилируем gcc 4.8.1 c -O2, смотрим на сгенерированный код

  Скрытый текст
T>

T>main:
T> subq $24, %rsp
T> movq %rsp, %rdi
T> movq $_ZTV1B+16, (%rsp)
T> call _ZN1B4funcEv
T> ... etc

T>_ZN1B4funcEv:
T> movl $2, %edx
T> movl $.LC0, %esi
T> movl $_ZSt4cout, %edi
T> jmp _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
T> ... etc

T>.LC0:
T> .string "B\n"



Это ничего не доказывает. Ну получили мы после оптимизации код, который ведет себя также как неоптимизированный, ну и что? Нужен пример, для которого оптимизация изменит поведение. А в таком тривиальном случае, компилятор заинлайнит виртуальные вызовы даже после "смены типа", причем сделает это правильно:

int
main (void)
{
  B b;
  bar (&b);
  ::new (&b) A;
  bar (&b);
  return 0;
}


в первом случае будет B::f(), а после "смены типа" A::f().
Re[9]: С++, как вызвать конструктор объекта по месту?
От: Кодт Россия  
Дата: 22.01.14 09:35
Оценка: +1
Здравствуйте, c-smile, Вы писали:

CS>Нет там полиморфных баз по условию задачи. Еще раз: рассматривается конкретная задача а не конь в ваакуме.


Полиморфная база там есть. (В противовес виртуальной базе и просто базе без виртуальных методов).
Конкретная задача озвучена тобой немножко в вакууме. Понятно, что ты пытаешься гоняться за производительностью, но непонятно, в каком масштабе и вообще зачем.

К>>Ну как что нарушает. Вагон неопределённого поведения.


CS>"Имя, сестра, имя!" Что конкретно неопределено?

CS>new(place), использование конструкторов или что?

Я уже сказал: пропатчивание объекта. Ты или стреляешь по памяти, или реконструируешь объект, тем самым инвалидируя предыдущий.

К>>Хирургия с подменой vfptr — это частный случай стрельбы по памяти.

CS>Где я там меняю vfptr?

Твои слова или не твои?

Мне нужно решить на самом деле обратную задачу
Вызвать placement new без вызова конструктора.

Для классов с виртуальными функциями new(oldobj) просто меняет (инициализирует)
vtbl
. А конструктор инициализирует поля. Мне надо вызвать new(place) но не конструктор.


К>>placement new — инвалидирует старый объект, ну и что, что почти такой же новый по тому же адресу возник. Как мы видели, компилятор может надеяться на то, что если объект валидный, то и его тип неизменный и известный заранее, и сэкономить на виртуальных вызовах.


CS>Не может он там экономить. Обращение всегда идет через pointer на базу (Thing).


Вообще-то, может.
Попробуй скомпилировать вот это на g++ 4.7 с параметрами -O2 и -O0, увидишь разницу.
#include <cstdio>
#include <memory>

struct Base
{
    virtual const char* whoami() const { return "Base"; }
};

struct Derived : Base
{
    Derived(Base&& b) : Base(b) {}
    virtual const char* whoami() const { return "Derived"; }
};

void patch(Base* b)
{
    Base t(std::move(*b));
    Derived* d = new(b)Derived(std::move(t));    
}

int main()
{
    Base ob;
    Base* pb = &ob;
    patch(pb);
    printf("%s %s\n", ob.whoami(), pb->whoami()); // -O2: Base Base | -O0: Base Derived
};

MSVC, кстати, даже в самых агрессивных режимах оптимизации форсирует виртуальный вызов от указателя/ссылки, даже если ему не давать повод что-либо заподозрить (закомментировать patch).

К>>Стандарт разрешает стрелять в ногу, но говорит, что скорую помощь и патологоанатома будут оплачивать жертва и её родственники.

CS>Николай, давай на технических деталях сфокусируемся, лирику люди поют в политике.

Технические детали — это твоё право делать всё, что угодно well-formed, но некоторые WF программы ведут к undefined behavior, а UB бывает предсказуемым и непредсказуемым, желаемым и нежелаемым.


И кстати, чтобы два раза не вставать.
На всякий случай напоминаю, что
— placement new operator вообще ничего не делает, а new expression вызывает конструктор. Нет такой штуки "вызвать предконструктор, но свалить из тела конструктора". Даже если с помощью исключения мы поймаем момент, когда vfptr уже настроен, а до конструирования членов ещё не дошли, — всё равно, будут проблемы.
— vfptr в ходе конструирования наследника настраивается много раз, по мере прохождения конструкторов баз — в отличие от дотнета, жабы и дельфей.
Перекуём баги на фичи!
Re[7]: С++, как вызвать конструктор объекта по месту?
От: Кодт Россия  
Дата: 22.01.14 09:36
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Вот твоя идея, но немного доработанная: http://ideone.com/5O6SFV


Выстави агрессивную оптимизацию — и обломишься.
Перекуём баги на фичи!
Re[7]: С++, как вызвать конструктор объекта по месту?
От: jazzer Россия Skype: enerjazzer
Дата: 22.01.14 10:25
Оценка:
Здравствуйте, c-smile, Вы писали:

J>>Это делает конструктор. Вернее, стандарт не специфицирует vtbl вообще, но по сути этим занимается конструктор в качестве первого шага, так как указатель на vtbl — это просто скрытый член, который надо проинициализировать.


CS>Это происходит между new и первым конструктором. Но это не суть важно.


То же самое надо сделать и при размещении объекта на стеке, и при инициализации каждой базы в цепочке баз. Нафига компилятору заниматься магией "между", если по сути это просто инициализация скрытого члена? Вот писал бы ты компилятор и тебе понадобилось бы вставить в объект скрытый член — ты бы как поступил? Вставил бы код его инициализации первой строчкой кода конструктора, чтоб потом просто этот конструктор звать в одну инструкцию? Или перед каждым вызовом конструктора впихивал бы код инициализации? С нулевой экономией причем — так как конструктор все равно потом звать надо.

J>>>>Для произвольного левого класса это невозможно сделать, если ты хочешь, чтоб код оставался соответствующим Стандарту. Если классы все под твоим контролем — юзай указатель pimpl, в него ты можешь засунуть все, что угодно (в т.ч. std::string), и не инициализировать, если он уже есть (ну или передавать указатель в конструктор, чтоб уж совсем не зависеть от его точного расположения в классе).


CS>Ищется решение которое вводит наиментшее кол-во сущностей и CPU penalty.


Простое разделение данных и виртуальной иерархии кода, который с этими данными работает — достаточно "наименьшее"?
Тебе ведь по сути это надо — менять обработчик, не трогая данные?
Ну так раздели их полностью.

Вообще, тебя ведь несколько раз попросили уже объяснить свою реальную задачу. Ради чего все эти пляски с подменами?

J>>Но если ты хочешь пользоваться встроенной системой типов С++ в соответствии со стандартом — придется соответствовать, не предполагая, в частности, никаких vtbl и прочего.


CS>Странно, все говорят про Стандарт, но никто так конкретно и не говорит чем мое решение его нарушает.

"твое решение" — это какое? С объединением — я процитировал стандарт. Исходное — ничего не нарушает (разве что ты не должен пользоваться старым указателем, только новым, которые возвращен из turn_thing_to), но кривое и не работает со сколь-нибудь интересными типами (ты и сам про это пишешь).

CS>Вообще какая-то странная ситуация получается. С одной стороны использование reinterpret_cast конструкций типа как легитимно в C++.

Оно очень условно легитимно. Прочитай соответствующий раздел стандарта. По сути вообще нет никаких гарантий со стороны стандарта относительно того, что вернет reinterpret_cast, за исключением очень небольшого количества оговоренных случаев.

CS>А с другой стороны вариация оного так сразу супротив Стандарта.

UB — оно в соответствии со стандартом UB, совсем не супротив
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[10]: С++, как вызвать конструктор объекта по месту?
От: k.o. Россия  
Дата: 22.01.14 10:46
Оценка:
Здравствуйте, Кодт, Вы писали:

К>>>placement new — инвалидирует старый объект, ну и что, что почти такой же новый по тому же адресу возник. Как мы видели, компилятор может надеяться на то, что если объект валидный, то и его тип неизменный и известный заранее, и сэкономить на виртуальных вызовах.


CS>>Не может он там экономить. Обращение всегда идет через pointer на базу (Thing).


К>Вообще-то, может.

К>Попробуй скомпилировать вот это на g++ 4.7 с параметрами -O2 и -O0, увидишь разницу.
  Скрытый текст
К>
К>#include <cstdio>
К>#include <memory>

К>struct Base
К>{
К>    virtual const char* whoami() const { return "Base"; }
К>};

К>struct Derived : Base
К>{
К>    Derived(Base&& b) : Base(b) {}
К>    virtual const char* whoami() const { return "Derived"; }
К>};

К>void patch(Base* b)
К>{
К>    Base t(std::move(*b));
К>    Derived* d = new(b)Derived(std::move(t));    
К>}

К>int main()
К>{
К>    Base ob;
К>    Base* pb = &ob;
К>    patch(pb);
К>    printf("%s %s\n", ob.whoami(), pb->whoami()); // -O2: Base Base | -O0: Base Derived
К>};
К>

К>MSVC, кстати, даже в самых агрессивных режимах оптимизации форсирует виртуальный вызов от указателя/ссылки, даже если ему не давать повод что-либо заподозрить (закомментировать patch).

А не бага-ли это в GCC? Даже если вместо patсh(pb) написать pb = new(pb)Derived; GCC все-равно использует "Base" к качестве результата pb->whoami().

int main()
{
    void *pmem = nullptr;
    Base *pb = nullptr;
    
    Derived o1;
    pmem = &o1;
    pb = new(pmem)Base;
    printf("%s\n", pb->whoami()); // ok: -O2: Base | -O0: Base
    
    Base o2;
    pmem = &o2;
    pb = new(pmem)Derived;
    printf("%s\n", pb->whoami()); // failed: -O2: Base | -O0: Derived
}


Что интересно, это происходит только если использовать память объекта созданного на стеке.

    Base *pb = new Base;
    pb = new(pb)Derived;
    printf("%s\n", pb->whoami()); // ok: -O2: Derived| -O0: Derived
Re[11]: С++, как вызвать конструктор объекта по месту?
От: Кодт Россия  
Дата: 22.01.14 11:50
Оценка:
Здравствуйте, k.o., Вы писали:

KO>А не бага-ли это в GCC? Даже если вместо patсh(pb) написать pb = new(pb)Derived; GCC все-равно использует "Base" к качестве результата pb->whoami().


Вообще какая-то муть. На -O2 получаем
int main()
{
    Derived o1;
    Base* pbb = new(&o1)Base();
    Base* pb1 = &o1;
    printf("\n");
    printf("%s\n", pb1->whoami()); // Base
    printf("%s\n", pbb->whoami()); // Base
    
    Base o2;
    Derived* pbd = new(&o2)Derived();
    Derived* pb2 = (Derived*)&o2;
    printf("\n");
    printf("%s\n", pb2->whoami()); // Base
    printf("%s\n", pbd->whoami()); // Base

    Base* o3 = &o1;
    Derived* pbm = new(o3)Derived();
    Base* pb3 = (Base*)o3;
    printf("\n");
    printf("%s\n", pb3->whoami()); // Derived
    printf("%s\n", pbm->whoami()); // Derived

    Base* o4 = &o2;
    Derived* pbn = new(o4)Derived();
    Base* pb4 = (Base*)o4;
    printf("\n");
    printf("%s\n", pb4->whoami()); // Base
    printf("%s\n", pbn->whoami()); // Base

    Base o5[1]; // или любое другое хранилище, например, intptr_t[12345]
    Derived* pbz = new(o5)Derived();
    Base* pb5 = (Base*)o5;
    printf("\n");
    printf("%s\n", pb5->whoami()); // Derived
    printf("%s\n", pbz->whoami()); // Derived
};


А вот интересно, это баг или неопределённое поведение. Думаю, что всё же второе, потому что мы конструируем объект поверх существующего, лежащего в автоматическом или статическом хранилище (для последнего — нужно o1-o5 вынести из функции или снабдить static'ами). Безотносительно того, будем ли предварительно инвалидировать объекты, вызывая у них деструкторы.
Ну не предназначены именованные объекты для того, чтобы над ними творили такую жестокость.
Перекуём баги на фичи!
Re[12]: С++, как вызвать конструктор объекта по месту?
От: k.o. Россия  
Дата: 22.01.14 12:39
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, k.o., Вы писали:


KO>>А не бага-ли это в GCC? Даже если вместо patсh(pb) написать pb = new(pb)Derived; GCC все-равно использует "Base" к качестве результата pb->whoami().


К>Вообще какая-то муть. На -O2 получаем

  Скрытый текст
К>
К>int main()
К>{
К>    Derived o1;
К>    Base* pbb = new(&o1)Base();
К>    Base* pb1 = &o1;
К>    printf("\n");
К>    printf("%s\n", pb1->whoami()); // Base
К>    printf("%s\n", pbb->whoami()); // Base
    
К>    Base o2;
К>    Derived* pbd = new(&o2)Derived();
К>    Derived* pb2 = (Derived*)&o2;
К>    printf("\n");
К>    printf("%s\n", pb2->whoami()); // Base
К>    printf("%s\n", pbd->whoami()); // Base

К>    Base* o3 = &o1;
К>    Derived* pbm = new(o3)Derived();
К>    Base* pb3 = (Base*)o3;
К>    printf("\n");
К>    printf("%s\n", pb3->whoami()); // Derived
К>    printf("%s\n", pbm->whoami()); // Derived

К>    Base* o4 = &o2;
К>    Derived* pbn = new(o4)Derived();
К>    Base* pb4 = (Base*)o4;
К>    printf("\n");
К>    printf("%s\n", pb4->whoami()); // Base
К>    printf("%s\n", pbn->whoami()); // Base

К>    Base o5[1]; // или любое другое хранилище, например, intptr_t[12345]
К>    Derived* pbz = new(o5)Derived();
К>    Base* pb5 = (Base*)o5;
К>    printf("\n");
К>    printf("%s\n", pb5->whoami()); // Derived
К>    printf("%s\n", pbz->whoami()); // Derived
К>};
К>

К>А вот интересно, это баг или неопределённое поведение. Думаю, что всё же второе, потому что мы конструируем объект поверх существующего, лежащего в автоматическом или статическом хранилище (для последнего — нужно o1-o5 вынести из функции или снабдить static'ами). Безотносительно того, будем ли предварительно инвалидировать объекты, вызывая у них деструкторы.

В стандарте я на это запрета не видел, при этом там подробно описывается в каких случаях такое использование storage автоматических объектов приведет к UB, соответственно в прочих случаях все должно работать.

К>Ну не предназначены именованные объекты для того, чтобы над ними творили такую жестокость.


А как же, например, какой-нибудь там, aligned_storage?
Re[13]: С++, как вызвать конструктор объекта по месту?
От: Кодт Россия  
Дата: 22.01.14 12:54
Оценка:
Здравствуйте, k.o., Вы писали:

К>>А вот интересно, это баг или неопределённое поведение. Думаю, что всё же второе, потому что мы конструируем объект поверх существующего, лежащего в автоматическом или статическом хранилище (для последнего — нужно o1-o5 вынести из функции или снабдить static'ами). Безотносительно того, будем ли предварительно инвалидировать объекты, вызывая у них деструкторы.


KO>В стандарте я на это запрета не видел, при этом там подробно описывается в каких случаях такое использование storage автоматических объектов приведет к UB, соответственно в прочих случаях все должно работать.


Возможно, что это ещё и дефект стандарта.
Ну вот не привиделось комитетчикам в страшном сне такое надругательство.

К>>Ну не предназначены именованные объекты для того, чтобы над ними творили такую жестокость.


KO>А как же, например, какой-нибудь там, aligned_storage?


aligned_storage — это хорошо выравненный POD. Его вообще можно memset'ить как угодно, — почему бы не использовать и для placement new.
Перекуём баги на фичи!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.