placement new
От: maks1180  
Дата: 10.12.22 17:46
Оценка:
Допустим есть 2 класса:
сlass A {
public: int v1; int v2;
};

class B: public сlass A {
int v3;
B() { v3 = 1; }
};

сначала я создал экземпляр сlass A через placement new
new(p)A();
поработал с ним, потом создал сlass B, на тот же месте где был Class A
new(p)B();

1) Есть ли гарантии, что v1 и v2 класс A и В будут по одинаковым смещениям ?
2) Есть ли гарантии, что конструктор класса B не перетрёт значения v1, v2 которые остались в памяти от прошлого экземпляра класса A ?

Таким образом я хочу динамически изменить класс объекта сохранив его в той же области памяти.
Или может можно как-то поменять v-table у объекта и он станет объектом другого класса ?

Такое изощерение для того что-бы не вызывать epoll_ctl(..., EPOLL_CTL_MOD) при смене адреса, который занимает около 4100 тактов.
===============================================
(реклама, удалена модератором)
Отредактировано 10.12.2022 17:55 maks1180 . Предыдущая версия . Еще …
Отредактировано 10.12.2022 17:53 maks1180 . Предыдущая версия .
Отредактировано 10.12.2022 17:47 maks1180 . Предыдущая версия .
Re: placement new
От: vopl Россия  
Дата: 10.12.22 17:50
Оценка: +2
Здравствуйте, maks1180, Вы писали:

M>Допустим есть 2 класса:

M>Class A {
M>public: int v1; int v2;
M>};
M>class B: public Class A {
M> int v3;
M> B() { v3 = 1; }
M>};

куски кода желательно оформлять именно как C/C++ код, ато их читать неудобно, глаза режет ..
Re: placement new
От: T4r4sB Россия  
Дата: 10.12.22 17:57
Оценка:
Здравствуйте, maks1180, Вы писали:

M>Таким образом я хочу динамически изменить класс объекта сохранив его в той же области памяти.


А можно задачу в общее общем виде? Не ту подзадачу, что ты сформулировал, а вот ту, которую реально надо решить?
Re: placement new
От: rg45 СССР  
Дата: 10.12.22 17:58
Оценка: :)
Здравствуйте, maks1180, Вы писали:

M>сначала я создал экземпляр сlass A через placement new

M>new(p)A();
M>поработал с ним, потом создал сlass B, на тот же месте где был Class A
M>new(p)B();

M>1) Есть ли гарантии, что v1 и v2 класс A и В будут по одинаковым смещениям ?

M>2) Есть ли гарантии, что конструктор класса B не перетрёт значения v1, v2 которые остались в памяти от прошлого экземпляра класса A ?

Согласно стандарту языка, использование одной и той же области памяти для размещения двух разных объектов порождает неопределенное поведение. Нельзя так делать.

P.S. Был не прав, каюсь. Подробности здесь: http://rsdn.org/forum/cpp/8426197.1
Автор: rg45
Дата: 12.12.22
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 11.12.2022 21:40 rg45 . Предыдущая версия .
Re[2]: placement new
От: maks1180  
Дата: 10.12.22 18:10
Оценка:
TB>А можно задачу в общее общем виде? Не ту подзадачу, что ты сформулировал, а вот ту, которую реально надо решить?

Есть сетевой сервис (приложение):
1) на каждое входящее соединение создаём объект класс ClientCommon и далее он отвечает за работу с этим соединением.
2) после общения с клиентом, мы понимает что он хочет от сервиса и должны создать заместо ClientCommon либо Client1 либо Client2
3) далее уже Client1 либо Client2 будут обрабатывать работу с данным соединением

Если меняется адрес объекта нужно вызвать epoll_ctl(..., EPOLL_CTL_MOD), который занимает около 4100 тактов.
Если сделать через прослойку указателей через std::deque я мерил занимает около 50 тактов, на каждый recv() (поступивший пакет), что может превысить штраф в 4100 тактов который нужно заплатить 1 раз при смене объекта.

Поэтому я решил не менять адрес объекта и не использовать прослойки указателей.
===============================================
(реклама, удалена модератором)
Отредактировано 10.12.2022 18:19 maks1180 . Предыдущая версия . Еще …
Отредактировано 10.12.2022 18:11 maks1180 . Предыдущая версия .
Re[2]: placement new
От: maks1180  
Дата: 10.12.22 18:12
Оценка:
R>Согласно стандарту языка, использование одной и той же области памяти для размещения двух разных объектов порождает неопределенное поведение. Нельзя так делать.

Даже если первый объект больше не будет использован после создания второго ?
===============================================
(реклама, удалена модератором)
Re[3]: placement new
От: T4r4sB Россия  
Дата: 10.12.22 18:18
Оценка:
Здравствуйте, maks1180, Вы писали:

M>Даже если первый объект больше не будет использован после создания второго ?


Компилятор предположит, что эти объекты никак друг к другу не относятся, то есть он может обращение к полю "предыдущего" объекта воспринять как обращение к неинициализированной памяти, например.
Re[3]: placement new
От: T4r4sB Россия  
Дата: 10.12.22 18:20
Оценка:
Здравствуйте, maks1180, Вы писали:

TB>>А можно задачу в общее общем виде? Не ту подзадачу, что ты сформулировал, а вот ту, которую реально надо решить?


M>Есть сетевой сервис (приложение):

M>1) на каждое входящее соединение создаём объект класс ClientCommon и далее он отвечает за работу с этим соединением.
M>2) после общения с клиентом, мы понимает что он хочет от сервиса и должны создать заместо ClientCommon либо Client1 либо Client2
M>3) далее уже Client1 либо Client2 будут обрабатывать работу с данным соединением

Неужели просто смувать результат в соответвующее поле конечного результата настолько дорого?
Re[4]: placement new
От: maks1180  
Дата: 10.12.22 18:22
Оценка:
TB>Неужели просто смувать результат в соответвующее поле конечного результата настолько дорого?

Не понял, что ты имеешь ввиду.
===============================================
(реклама, удалена модератором)
Re[5]: placement new
От: T4r4sB Россия  
Дата: 10.12.22 18:33
Оценка:
Здравствуйте, maks1180, Вы писали:

TB>>Неужели просто смувать результат в соответвующее поле конечного результата настолько дорого?


M>Не понял, что ты имеешь ввиду.

class ClientCommon {...};
class Client1 { ClientCommon  common; ...};
class Client2 { ClientCommon  common; ...};

ClientCommon cc;
foo(cc);
if (condition) {
  Client1 cl1;
  cl1.common = std::move(cc);
  ...
} else {
  Client2 cl2;
  cl2.common = std::move(cc);
  ...
}

Это если не хочется лишнюю индирекцию в коде иметь. Про std::deque не понял — почему если прослойка указателей, то std::deque?
Re[6]: placement new
От: maks1180  
Дата: 10.12.22 18:39
Оценка:
TB>
TB>class ClientCommon {...};
TB>class Client1 { ClientCommon  common; ...};
TB>class Client2 { ClientCommon  common; ...};

TB>ClientCommon cc;
TB>foo(cc);
TB>if (condition) {
TB>  Client1 cl1;
TB>  cl1.common = std::move(cc);
TB>  ...
TB>} else {
TB>  Client2 cl2;
TB>  cl2.common = std::move(cc);
TB>  ...
TB>}
TB>


Адрес же cl1 и cc будет отличаться.

TB>Это если не хочется лишнюю индирекцию в коде иметь. Про std::deque не понял — почему если прослойка указателей, то std::deque?

std::vector тормоза сильные может дать при росте.
===============================================
(реклама, удалена модератором)
Re[7]: placement new
От: T4r4sB Россия  
Дата: 10.12.22 18:46
Оценка:
Здравствуйте, maks1180, Вы писали:

M>Адрес же cl1 и cc будет отличаться.


А там типа этот адрес уже сохранён в 100500 мест, и надо во всех этих местах обновить значение, да?

M>std::vector тормоза сильные может дать при росте.


Ну так он в какой-то момент прекращает расти.
А ещё если у тебя х64 и с маппингом поиграться, то можно сделать бесконечный вектор без реаллокаций.
Re[3]: placement new
От: rg45 СССР  
Дата: 10.12.22 19:19
Оценка:
Здравствуйте, maks1180, Вы писали:

R>>Согласно стандарту языка, использование одной и той же области памяти для размещения двух разных объектов порождает неопределенное поведение. Нельзя так делать.


M>Даже если первый объект больше не будет использован после создания второго ?


А почему это он не будет использован? Время жизни объекта продолжатеся и, как минимум, для него еще будет вызван деструктор (который формально есть даже у тривиальных типов).
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[4]: placement new
От: T4r4sB Россия  
Дата: 10.12.22 19:31
Оценка:
Здравствуйте, rg45, Вы писали:

R> и, как минимум, для него еще будет вызван деструктор


Это после placement new-то?
Re[5]: placement new
От: rg45 СССР  
Дата: 10.12.22 19:39
Оценка:
Здравствуйте, T4r4sB, Вы писали:

R>> и, как минимум, для него еще будет вызван деструктор


TB>Это после placement new-то?


Я не понял, а что, если объект был создан через placement new, то корректно завершать его время жизни не обязательно? Или ты у меня увидел где-то слово "автоматически"? Имелось в виду, что well-formed программа обязана обеспечить корректное завершение времени жизни объекта, тем или иным образом.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 10.12.2022 19:42 rg45 . Предыдущая версия . Еще …
Отредактировано 10.12.2022 19:41 rg45 . Предыдущая версия .
Re: placement new
От: Sm0ke Россия ksi
Дата: 10.12.22 22:42
Оценка: +1
Здравствуйте, maks1180, Вы писали:

Я бы предложил сделать обёртку, которая хранит экземпляр для смены. Назовём его keep

https://godbolt.org/z/34j8s6rxY
#include <concepts>
#include <type_traits>
#include <algorithm>

template <typename T, typename ... U>
concept c_any_of = ( std::same_as<T, U> || ... );

template <typename T_base, std::derived_from<T_base> ... T_sub>
struct keep {
  static constexpr std::size_t
  s_max_size = std::max<std::size_t>({sizeof(T_base), sizeof(T_sub) ...});
  
  static constexpr std::size_t
  s_max_align = std::max<std::size_t>({alignof(T_base), alignof(T_sub) ...});
  
  static constexpr bool
  s_all_trivial_des = (
    std::is_trivially_destructible_v<T_base> &&
    (std::is_trivially_destructible_v<T_sub> && ...)
  );
  
  using t_data = std::aligned_storage_t<s_max_size, s_max_align>;
  using pointer = T_base *;
  
  private:
  t_data m_data;
  public:
  pointer m_base = nullptr;
  
  ~keep() requires(! s_all_trivial_des) {
    clear();
  }

  template <c_any_of<T_base, T_sub ...> T, typename ... T_args>
  T * emplace(T_args && ... p_args) {
    clear();
    T * ret = new (&m_data) T(std::forward<T_args>(p_args) ...); 
    m_base = ret;
    return ret;
  }
  
  operator bool () const {
    return m_base != nullptr;
  }

  pointer operator -> () {
    return m_base;
  }

  void clear() {
    if( m_base != nullptr ) {
      if constexpr( ! s_all_trivial_des ) { m_base->~T_base(); }
      m_base = nullptr;
    }
  }
};


Тут keep<some_base, some_derived1, some_derived2> Хранит указатель на базовый класс в поле m_base , Ведь нет гарантий, что адреса в указателе при разных emplace<some_derived_xxx>() будут одинаковыми. Например при множественном наследовании от других классов. Кроме того при (m_base == nullptr) мы точно знаем, что объект keep пуст.

Если надо в keep создать сперва базовый класс, а потом пересоздать один из наследников, но свойства базового сохранить, то я думаю стоит эти свойства объединить в отдельную структуру. Чтобы можно было их засэйвить (скопировать или мувнуть) в локальную переменную и потом пробросить в конструктор наследника через emplace<xd>(v_params).

И напоследок: Если у целевых классов есть нетривиальный деструктор, то в базовом деструктор должен быть виртуальным.
Отредактировано 10.12.2022 23:11 Sm0ke . Предыдущая версия . Еще …
Отредактировано 10.12.2022 22:55 Sm0ke . Предыдущая версия .
Отредактировано 10.12.2022 22:53 Sm0ke . Предыдущая версия .
Отредактировано 10.12.2022 22:51 Sm0ke . Предыдущая версия .
Отредактировано 10.12.2022 22:47 Sm0ke . Предыдущая версия .
Отредактировано 10.12.2022 22:45 Sm0ke . Предыдущая версия .
Re: placement new
От: andyp  
Дата: 10.12.22 23:26
Оценка:
Здравствуйте, maks1180, Вы писали:


M>Таким образом я хочу динамически изменить класс объекта сохранив его в той же области памяти.

M>Или может можно как-то поменять v-table у объекта и он станет объектом другого класса ?

Т.е. нужен юнион, объекты внутри которого хранятся по одному адресу?

Одна опция — замутить свой variant. Ну или взять кусок реализации в libstdc++, которая вроде этому условию удовлетворяет.
Re[2]: placement new
От: maks1180  
Дата: 11.12.22 06:42
Оценка:
S> T * ret = new (&m_data) T(std::forward<T_args>(p_args) ...);

Разве ret может отличатся от &m_data ?

Как показывает мой опыт, добавление промежуточного указателя m_base, может повлечь снижение производительности за счёт промаха кэша.
===============================================
(реклама, удалена модератором)
Отредактировано 11.12.2022 6:51 maks1180 . Предыдущая версия .
Re[2]: placement new
От: maks1180  
Дата: 11.12.22 06:43
Оценка:
M>>Таким образом я хочу динамически изменить класс объекта сохранив его в той же области памяти.
M>>Или может можно как-то поменять v-table у объекта и он станет объектом другого класса ?

A>Т.е. нужен юнион, объекты внутри которого хранятся по одному адресу?


Да. Но нужно как-то поменять v-table у объекта и он станет объектом другого класса ?

A>Одна опция — замутить свой variant. Ну или взять кусок реализации в libstdc++, которая вроде этому условию удовлетворяет.
===============================================
(реклама, удалена модератором)
Re: placement new
От: vopl Россия  
Дата: 11.12.22 08:20
Оценка: +1
Здравствуйте, maks1180, Вы писали:

M>Допустим есть 2 класса:

M>сlass A {
M>public: int v1; int v2;
M>};

M>class B: public сlass A {

M> int v3;
M> B() { v3 = 1; }
M>};

M>сначала я создал экземпляр сlass A через placement new

M>new(p)A();
M>поработал с ним, потом создал сlass B, на тот же месте где был Class A
M>new(p)B();

M>1) Есть ли гарантии, что v1 и v2 класс A и В будут по одинаковым смещениям ?

M>2) Есть ли гарантии, что конструктор класса B не перетрёт значения v1, v2 которые остались в памяти от прошлого экземпляра класса A ?

M>Таким образом я хочу динамически изменить класс объекта сохранив его в той же области памяти.

M>Или может можно как-то поменять v-table у объекта и он станет объектом другого класса ?

M>Такое изощерение для того что-бы не вызывать epoll_ctl(..., EPOLL_CTL_MOD) при смене адреса, который занимает около 4100 тактов.


Желаемых гарантий нет. Но, первую можно самому обеспечить если правильно сформировать лайаут, да еще и ассертами его провалидировать. По поводу второй — наоборот, есть гарантия что конструирование нового объекта на месте имеющегося — UB, так точно делать нельзя. Но, если сначала разрушить оригинаотный объект а потом в этой памяти построить новый — то так вполне можно делать. В общем, если аккуратно все оформить — то в принципе все получится
#include <memory>

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
class A
{
public:
    int v1;
    int v2;

    virtual ~A(){}
};

class B: public A
{
public:
    int v3;
    B(A a)
        : A{std::move(a)}
        , v3{1}
    {}
};

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
union Space
{
    char stub;
    A a;
    B b;
    Space() : stub{} {}
    ~Space() {}
} space{};

//ну, это и так понятно
static_assert(static_cast<void*>(&space.a) == static_cast<void*>(&space.b));

//важный инвариант: самостоятельная часть A и под-часть A из B - лежат в одном и том же месте
static_assert(&space.a == static_cast<A*>(&space.b));


/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
A* storedPa{};
void store(A* pa)
{
    storedPa = pa;
}

void useStoredPointer()
{
    storedPa->v1 = 220;
    int k = storedPa->v2;
}

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
int main()
{
    //создаем общий функционал A
    new(&space.a) A;

    store(&space.a);
    useStoredPointer();

    //немного с ним поработали, поняли что его надо заменить на уточненный функционал B
    {
        //запомним общее состояние
        A aState = std::move(space.a);
        //разрушим общий оригинал
        space.a.~A();
        //пересоздадим его как уточненный, заодно зарядим его прошлым состоянием
        new(&space.b) B{std::move(aState)};
    }

    //теперь работаем с B
    //причем так что его общая под-часть A имеет такой же адрес как и раньше до B
    useStoredPointer();

    //поработали, заканчиваем время жизни B
    space.b.~B();

    return 0;
}
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.