С++, как вызвать конструктор объекта по месту?
От: c-smile Канада http://terrainformatica.com
Дата: 20.01.14 01:39
Оценка:
Скажем есть указатель на уже выделенную память под объект.
Как вызвать конструктор по этому месту?

Вариант с placement new я знаю:

void *pmem = ...;
Thing* pt = new(pmem) Thing(params);


Это единственный вариант?
Re: С++, как вызвать конструктор объекта по месту?
От: jazzer Россия Skype: enerjazzer
Дата: 20.01.14 03:29
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Скажем есть указатель на уже выделенную память под объект.

CS>Как вызвать конструктор по этому месту?

CS>Вариант с placement new я знаю:


CS>
CS>void *pmem = ...;
CS>Thing* pt = new(pmem) Thing(params);
CS>


CS>Это единственный вариант?


Да, это именно он и есть, а чем он не устраивает?
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: С++, как вызвать конструктор объекта по месту?
От: jyuyjiyuijyu  
Дата: 20.01.14 03:44
Оценка: 42 (1)
Здравствуйте, c-smile, Вы писали:

CS>Это единственный вариант?


а так не ?

((ololo*)p)->ololo::ololo();
Re[2]: С++, как вызвать конструктор объекта по месту?
От: Alexander G Украина  
Дата: 20.01.14 08:09
Оценка: 43 (2)
Здравствуйте, jyuyjiyuijyu, Вы писали:

J>
J>((ololo*)p)->ololo::ololo();
J>


Так только в MSVC
Русский военный корабль идёт ко дну!
Re[2]: С++, как вызвать конструктор объекта по месту?
От: c-smile Канада http://terrainformatica.com
Дата: 20.01.14 17:04
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Да, это именно он и есть, а чем он не устраивает?


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

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

Контекст проблемы: http://stackoverflow.com/questions/21212379/changing-vtbl-of-existing-object-on-the-fly-dynamic-subclassing
Re[3]: С++, как вызвать конструктор объекта по месту?
От: night beast СССР  
Дата: 20.01.14 17:18
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Мне нужно решить на самом деле обратную задачу

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

CS>Для классов с виртуальными функциями new(oldobj) просто меняет (инициализирует)

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

дык это, может мемкопи первых нескольких байт?

CS>Контекст проблемы: http://stackoverflow.com/questions/21212379/changing-vtbl-of-existing-object-on-the-fly-dynamic-subclassing
Re[4]: С++, как вызвать конструктор объекта по месту?
От: c-smile Канада http://terrainformatica.com
Дата: 20.01.14 17:27
Оценка:
Здравствуйте, night beast, Вы писали:

NB>дык это, может мемкопи первых нескольких байт?


a. Хотелось бы переносимое решение. Это компилируется в MSVC, Apple XCode и GCC.
b. Если с "to" можно еще как-то разобраться, то что делать с from? Откуда копировать vtbl ?
Re[5]: С++, как вызвать конструктор объекта по месту?
От: night beast СССР  
Дата: 20.01.14 17:33
Оценка:
Здравствуйте, c-smile, Вы писали:

NB>>дык это, может мемкопи первых нескольких байт?


CS>a. Хотелось бы переносимое решение. Это компилируется в MSVC, Apple XCode и GCC.


общая пустая база есть?

CS>b. Если с "to" можно еще как-то разобраться, то что делать с from? Откуда копировать vtbl ?


не подумал
Re[5]: С++, как вызвать конструктор объекта по месту?
От: night beast СССР  
Дата: 20.01.14 19:37
Оценка:
Здравствуйте, c-smile, Вы писали:

NB>>дык это, может мемкопи первых нескольких байт?


CS>a. Хотелось бы переносимое решение. Это компилируется в MSVC, Apple XCode и GCC.

CS>b. Если с "to" можно еще как-то разобраться, то что делать с from? Откуда копировать vtbl ?

существующий код менять нельзя?
может что-то вроде этого
Автор: alnsn
Дата: 02.05.04
подойдет?
Re[6]: С++, как вызвать конструктор объекта по месту?
От: c-smile Канада http://terrainformatica.com
Дата: 20.01.14 22:01
Оценка:
Здравствуйте, night beast, Вы писали:

NB>существующий код менять нельзя?


Можно менять если что-то значительно лучше.

NB>может что-то вроде этого
Автор: alnsn
Дата: 02.05.04
подойдет?


Я солидарен с кодт: "Сделали всё то же, что делает компилятор, только руками."
Как-то не радует руками прописывать vtbl. В реальности там что-то под 50 методов. Да еще и super calls используются.
Re[3]: С++, как вызвать конструктор объекта по месту?
От: jazzer Россия Skype: enerjazzer
Дата: 21.01.14 00:56
Оценка: +2 :)
Здравствуйте, c-smile, Вы писали:

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


J>>Да, это именно он и есть, а чем он не устраивает?


CS>Мне нужно решить на самом деле обратную задачу

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

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

CS>Для классов с виртуальными функциями new(oldobj) просто меняет (инициализирует)

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

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


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

Ну и по-хорошему ты еще должен корректно прибить тот объект, который там лежал, вызвав его деструктор (хотя с точки зрения lifetime это и не обязательно — достаточно факта переиспользования памяти).
struct Thing
{
  struct ThingImpl;
  typedef std::unique_ptr<ThingImpl> Pimpl;

  Thing(Pimpl p) : pimpl_(p) {}
  // другие нормальные инициализирующие pimpl_ конструкторы

  virtual ~Thing() {} // pimpl_ будет прибит тут автоматом

  template<class T>
  T* mutate() {
    Pimpl tmp( pimpl_ ); // теперь this своим pimpl-ом не владеет
    ~Thing(); // естественно, деструктор должен быть виртуальным
    return new(this) T(tmp);
  }

protected: // наследникам нужен доступ к потрохам
  Pimpl pimpl_;
};
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[7]: С++, как вызвать конструктор объекта по месту?
От: night beast СССР  
Дата: 21.01.14 02:43
Оценка:
Здравствуйте, c-smile, Вы писали:

NB>>может что-то вроде этого
Автор: alnsn
Дата: 02.05.04
подойдет?


CS>Я солидарен с кодт: "Сделали всё то же, что делает компилятор, только руками."


все так. но к этой vtbl ты имеешь доступ.

CS>Как-то не радует руками прописывать vtbl. В реальности там что-то под 50 методов. Да еще и super calls используются.


может есть возможность отделить данные от интерфейса. через предложенный Pimp или что-то вроде optional'а сделать:

struct data {};

struct test {

   optional< data > m_data;

   test ( NO_INIT ) { m_data.is_valid = true; }

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

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


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


J>>>Да, это именно он и есть, а чем он не устраивает?


CS>>Мне нужно решить на самом деле обратную задачу

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

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


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

CS>>Для классов с виртуальными функциями new(oldobj) просто меняет (инициализирует)

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

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


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


pimpl это a) лишний redirection level и б) потребность указания одних и тех же функций два раза.

Собственно vtbl и есть pimpl если смотреть на абстрактный класс и его специализацию/имплементацию.

J>Ну и по-хорошему ты еще должен корректно прибить тот объект, который там лежал, вызвав его деструктор (хотя с точки зрения lifetime это и не обязательно — достаточно факта переиспользования памяти).


Не надо там ничего прибивать. Ни до ни после turn_thing_to.
Поля остаются теми же и по тому же offsetof.
C++ ведь разрешает работать с конструкциями:
union {
  Thing t1;
  OtherThing t2;
}

мой вариант это то же самое только несколько иначе описанный.
Re[5]: С++, как вызвать конструктор объекта по месту?
От: jazzer Россия Skype: enerjazzer
Дата: 21.01.14 06:38
Оценка: 27 (2)
Здравствуйте, c-smile, Вы писали:

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


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


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


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

Так что сермяжность очень простая: new выделяет память, вызывает конструктор, возвращает указатель нужного типа.
placement new убирает первый шаг, оставляя вызов конструктора и возврат указателя нужного типа.
Если ты убираешь и конструктор, остается просто возврат указателя — это reinterpret_cast.

CS>>>Для классов с виртуальными функциями new(oldobj) просто меняет (инициализирует)

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

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


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


CS>pimpl это a) лишний redirection level и б) потребность указания одних и тех же функций два раза.

CS>Собственно vtbl и есть pimpl если смотреть на абстрактный класс и его специализацию/имплементацию.

Да, это pimpl таблицы методов. Но тебе ж еще и данные нужны

J>>Ну и по-хорошему ты еще должен корректно прибить тот объект, который там лежал, вызвав его деструктор (хотя с точки зрения lifetime это и не обязательно — достаточно факта переиспользования памяти).


CS>Не надо там ничего прибивать. Ни до ни после turn_thing_to.

CS>Поля остаются теми же и по тому же offsetof.
Ты можешь это гарантировать? И в случае множественного наследования тоже?
Для начала, размещение полей вообще никак не специфицировано, если тип не standard layout (а это как раз твой случай).

Так что ты уж определись — либо ты хочешь иметь код, соответствующий стандарту (как, например, код, который я привел), либо код, который "работает на доступных мне компиляторах".
Если второе — да, можешь предполагать что угодно, и проще всего вообще будет тупо вломиться в объект и поменять его внутренний указатель на vtbl, нужную тебе, и не заморачиваться с конструкторами, placement new и прочим — дешево и сердито.

CS>C++ ведь разрешает работать с конструкциями:

CS>
CS>union {
CS>  Thing t1;
CS>  OtherThing t2;
CS>} 
CS>

CS>мой вариант это то же самое только несколько иначе описанный.

Очень ограниченно разрешает. Ты не можешь записать в t1, а потом прочитать из t2. Либо одно, либо другое. Хочешь поменять — прибей одно, создай другое, читай [class.union]:

4 [ Note: In general, one must use explicit destructor calls and placement new operators to change the active
member of a union
. —end note ] [ Example: Consider an object u of a union type U having non-static data
members m of type M and n of type N. If M has a non-trivial destructor and N has a non-trivial constructor
(for instance, if they declare or inherit virtual functions), the active member of u can be safely switched
from m to n using the destructor and placement new operator as follows:

u.m.~M();
new (&u.n) N;
—end example ]

С объединениями из standard layout типов правила немного слабее, но это не твой случай из-за виртуальностей.
Если ты собираешься писать standard-compliant код, конечно же.

Если хочешь — сделай явный указатель на таблицу методов, сохраняя свои классы standard layout, и меняй потом эту таблицу руками как обычный член — с этим проблем нет.
Но если ты хочешь пользоваться встроенной системой типов С++ в соответствии со стандартом — придется соответствовать, не предполагая, в частности, никаких vtbl и прочего.
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[6]: С++, как вызвать конструктор объекта по месту?
От: rusted Беларусь  
Дата: 21.01.14 07:42
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Если второе — да, можешь предполагать что угодно, и проще всего вообще будет тупо вломиться в объект и поменять его внутренний указатель на vtbl, нужную тебе, и не заморачиваться с конструкторами, placement new и прочим — дешево и сердито.


Только нет гарантий, что это будет работать всегда. Например, при оптимизации компилятор может в каком-то месте определить, что тип объекта известен, и просто принлайнить вызовы некоторых виртуальных функций. И придется долго вылавливать почему на некоторых сборках с оптимизацией что-то работает не так.
Re[3]: С++, как вызвать конструктор объекта по месту?
От: k.o. Россия  
Дата: 21.01.14 09:08
Оценка: 27 (2)
Здравствуйте, c-smile, Вы писали:

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


J>>Да, это именно он и есть, а чем он не устраивает?


CS>Мне нужно решить на самом деле обратную задачу

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

CS>Для классов с виртуальными функциями new(oldobj) просто меняет (инициализирует)

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

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


Может имеет смысл вынести данные из Thing?

struct Data
{
    int a;
};

struct Storage
{
    // здесь, возможно, будет лучше использовать aligned_storage для Data и Thing/OtherThing/YetAnotherThing.
    Data data;
    Thing thing;
};


Если создавать Thing только как часть Storage, вполне можно будет вычислять смещение Data относительно Thing::this в compile-time, т.е. можно будет сделать что-то вроде

class Thing
{
    ...
    Data *data()
    {
        return ((char *)this) + offset;
    }
    ...

    virtual int value() { return data()->a; }
};


Тогда при пересоздании Thing, данные, гарантировано не изменятся и скорость работы с ними должна быть такой же, как если бы они лежали в Thing.
Re[7]: С++, как вызвать конструктор объекта по месту?
От: cserg  
Дата: 21.01.14 09:23
Оценка: +1
Здравствуйте, c-smile, Вы писали:

Если boost перевариваете, то можно использовать aligned_storage.

#include <iostream>
#include <string>
#include "boost/aligned_storage.hpp"

using namespace std;

struct NO_INIT {};

struct ThingData {
    int a;
    string s;
    ThingData(int v): a (v), s("test") { std::cout << "create ThingData\n"; }
    ~ThingData() { std::cout << "destroy ThingData\n"; }
};

class Thing {
    typedef ThingData data_type;
    typedef boost::aligned_storage<sizeof(data_type),
            boost::alignment_of<data_type>::value>::type storage_type;
    storage_type storage;

protected:
    Thing(NO_INIT) {}
    data_type* get_data() {return reinterpret_cast<data_type*>(&storage);}
    const data_type* get_data() const {return reinterpret_cast<const data_type*>(&storage);}

public:
    Thing(int v = 0) { new (&storage) data_type(v); }
    virtual ~Thing() { (*get_data()).~data_type(); }

    virtual const char * type_name(){ return "Thing"; }
    virtual int value() const { return get_data()->a; }
    virtual string str() const { return get_data()->s; }
};


Если boost не нравится, то можно выдернуть из LLVM похожий класс. Называется AlignedCharArrayUnion, содержится в llvm/Support/AlignOf.h
Re[8]: С++, как вызвать конструктор объекта по месту?
От: night beast СССР  
Дата: 21.01.14 09:27
Оценка:
Здравствуйте, cserg, Вы писали:

C>Если boost перевариваете, то можно использовать aligned_storage.


+1.

C>
C>#include <iostream>
C>#include <string>
C>#include "boost/aligned_storage.hpp"

C>


осталось сделать следующий щаг: завернуть это в класс, и получим что-то типа optional, только без доп.мембера.
Re[3]: С++, как вызвать конструктор объекта по месту?
От: Кодт Россия  
Дата: 21.01.14 12:14
Оценка:
Здравствуйте, c-smile, Вы писали:

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


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

// ООП (паттерн Стратегия)
struct IOperator
{
  virtual void doit(Operand& operand) const = 0;
  virtual const char* whoami() const = 0;
};

// Си или ФП
struct OperatorVTBL
{
  void (*doit)(Operand& operand);
  const char* (*whoami)();
  const char* itsme;
};

struct Operand
{
  int f1, f2;
  IOperator* op; // так
  OperatorVTBL* vp; // этак

  void doit() { op->doit(*this);  }
  const char* whoami() const { return op->whoami(); }
};

struct Summator : IOperator
{
  void doit(Operand& operand) { operand.f1 += operand.f2; }
  const char* whoami() const { return "+"; }
} summator_v1;

OperatorVTBL summator_v2 = {
  [](Operator& operand)->void { operand.f1 += operand.f2; },
  []()->const char* { return "+"; },
  "+",
};

Сишный способ даёт на одну косвенность меньше. А если в интерфейсе только одна функция, то можно вообще сразу эту функцию присваивать (предельный случай идиомы NVI).


Ну и наконец, маленький, но странный хак. Срезка наооборот.
#include <cstdio>
#include <memory>

struct Base
{
    int x, y, z;
    Base(int i) : x(i), y(i+i), z(i*i) {}
    virtual void whoami() { printf("%p base %d %d %d\n", this, x, y, z); }
};

struct Derived : Base
{
    Derived(Base&& b) : Base(b) {}
    virtual void whoami() { printf("%p derived %d %d %d\n", this, x, y, z); }
};

int main()
{
    Base b(3);
    Base* p = &b;

    b.whoami();   // 000000000022fd50 base 3 6 9
    p->whoami();  // 000000000022fd50 base 3 6 9

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

    printf("-----\n");
    b.whoami();   // 000000000022fd50 base 3 6 9
    p->whoami();  // 000000000022fd50 derived 3 6 9
    d->whoami();  // 000000000022fd50 derived 3 6 9
};

Обрати внимание, что компилятор не ждёт подлянки с подменой типа и немножко оптимизирует.
Перекуём баги на фичи!
Re[3]: С++, как вызвать конструктор объекта по месту?
От: Evgeny.Panasyuk Россия  
Дата: 21.01.14 16:47
Оценка: 4 (1)
Здравствуйте, c-smile, Вы писали:

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


SO.
Можно использовать boost::variant для меняющихся частей. Например:
struct ThingData
{
    int f1;
    int f2;
};

struct Summator
{
    void doAction1(ThingData &self)  { self.f1 += self.f2; }
    const char* type_name() { return "Summator"; }
};

struct Substractor
{
    void doAction1(ThingData &self)  { self.f1 -= self.f2; }
    const char* type_name() { return "Substractor"; }
};

using Thing = SubVariant<ThingData, Summator, Substractor>;

int main()
{
    auto test = [](auto &self, auto &sub)
    {
        sub.doAction1(self);
        cout << sub.type_name() << " " << self.f1 << " " << self.f2 << endl;
    };

    Thing x = {{5, 7}, Summator{}};
    apply(test, x);
    x.sub = Substractor{};
    apply(test, x);

    cout << "size: " << sizeof(x.sub) << endl;
}
Вывод:
Summator 12 7
Substractor 5 7
size: 2

  full code
#define BOOST_VARIANT_MINIMIZE_SIZE

#include <boost/variant.hpp>
#include <type_traits>
#include <functional>
#include <iostream>
#include <utility>

using namespace std;

/****************************************************************/
// Boost.Variant requires result_type:
template<typename T, typename F>
struct ResultType
{
     mutable F f;
     using result_type = T;

     template<typename ...Args> T operator()(Args&& ...args) const
     {
         return f(forward<Args>(args)...);
     }
};

template<typename T, typename F>
auto make_result_type(F &&f)
{
    return ResultType<T, typename decay<F>::type>{forward<F>(f)};
}
/****************************************************************/
// Proof-of-Concept
template<typename Base, typename ...Ts>
struct SubVariant
{
    Base shared_data;
    boost::variant<Ts...> sub;

    template<typename Visitor>
    friend auto apply(Visitor visitor, SubVariant &operand)
    {
        using result_type = typename common_type
        <
            decltype( visitor(shared_data, declval<Ts&>()) )...
        >::type;

        return boost::apply_visitor(make_result_type<result_type>([&](auto &x)
        {
            return visitor(operand.shared_data, x);
        }), operand.sub);
    }
};
/****************************************************************/
// Demo:

struct ThingData
{
    int f1;
    int f2;
};

struct Summator
{
    void doAction1(ThingData &self)  { self.f1 += self.f2; }
    const char* type_name() { return "Summator"; }
};

struct Substractor
{
    void doAction1(ThingData &self)  { self.f1 -= self.f2; }
    const char* type_name() { return "Substractor"; }
};

using Thing = SubVariant<ThingData, Summator, Substractor>;

int main()
{
    auto test = [](auto &self, auto &sub)
    {
        sub.doAction1(self);
        cout << sub.type_name() << " " << self.f1 << " " << self.f2 << endl;
    };
    
    Thing x = {{5, 7}, Summator{}};
    apply(test, x);
    x.sub = Substractor{};
    apply(test, x);

    cout << "size: " << sizeof(x.sub) << endl;
}
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.