Допустим есть 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 тактов.
Здравствуйте, 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++ код, ато их читать неудобно, глаза режет ..
Здравствуйте, 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 ?
Согласно стандарту языка, использование одной и той же области памяти для размещения двух разных объектов порождает неопределенное поведение. Нельзя так делать.
TB>А можно задачу в общее общем виде? Не ту подзадачу, что ты сформулировал, а вот ту, которую реально надо решить?
Есть сетевой сервис (приложение):
1) на каждое входящее соединение создаём объект класс ClientCommon и далее он отвечает за работу с этим соединением.
2) после общения с клиентом, мы понимает что он хочет от сервиса и должны создать заместо ClientCommon либо Client1 либо Client2
3) далее уже Client1 либо Client2 будут обрабатывать работу с данным соединением
Если меняется адрес объекта нужно вызвать epoll_ctl(..., EPOLL_CTL_MOD), который занимает около 4100 тактов.
Если сделать через прослойку указателей через std::deque я мерил занимает около 50 тактов, на каждый recv() (поступивший пакет), что может превысить штраф в 4100 тактов который нужно заплатить 1 раз при смене объекта.
Поэтому я решил не менять адрес объекта и не использовать прослойки указателей.
R>Согласно стандарту языка, использование одной и той же области памяти для размещения двух разных объектов порождает неопределенное поведение. Нельзя так делать.
Даже если первый объект больше не будет использован после создания второго ?
Здравствуйте, maks1180, Вы писали:
M>Даже если первый объект больше не будет использован после создания второго ?
Компилятор предположит, что эти объекты никак друг к другу не относятся, то есть он может обращение к полю "предыдущего" объекта воспринять как обращение к неинициализированной памяти, например.
Здравствуйте, maks1180, Вы писали:
TB>>А можно задачу в общее общем виде? Не ту подзадачу, что ты сформулировал, а вот ту, которую реально надо решить?
M>Есть сетевой сервис (приложение): M>1) на каждое входящее соединение создаём объект класс ClientCommon и далее он отвечает за работу с этим соединением. M>2) после общения с клиентом, мы понимает что он хочет от сервиса и должны создать заместо ClientCommon либо Client1 либо Client2 M>3) далее уже Client1 либо Client2 будут обрабатывать работу с данным соединением
Неужели просто смувать результат в соответвующее поле конечного результата настолько дорого?
Здравствуйте, 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?
Адрес же cl1 и cc будет отличаться.
TB>Это если не хочется лишнюю индирекцию в коде иметь. Про std::deque не понял — почему если прослойка указателей, то std::deque?
std::vector тормоза сильные может дать при росте.
Здравствуйте, maks1180, Вы писали:
M>Адрес же cl1 и cc будет отличаться.
А там типа этот адрес уже сохранён в 100500 мест, и надо во всех этих местах обновить значение, да?
M>std::vector тормоза сильные может дать при росте.
Ну так он в какой-то момент прекращает расти.
А ещё если у тебя х64 и с маппингом поиграться, то можно сделать бесконечный вектор без реаллокаций.
Здравствуйте, maks1180, Вы писали:
R>>Согласно стандарту языка, использование одной и той же области памяти для размещения двух разных объектов порождает неопределенное поведение. Нельзя так делать.
M>Даже если первый объект больше не будет использован после создания второго ?
А почему это он не будет использован? Время жизни объекта продолжатеся и, как минимум, для него еще будет вызван деструктор (который формально есть даже у тривиальных типов).
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, T4r4sB, Вы писали:
R>> и, как минимум, для него еще будет вызван деструктор
TB>Это после placement new-то?
Я не понял, а что, если объект был создан через placement new, то корректно завершать его время жизни не обязательно? Или ты у меня увидел где-то слово "автоматически"? Имелось в виду, что well-formed программа обязана обеспечить корректное завершение времени жизни объекта, тем или иным образом.
--
Не можешь достичь желаемого — пожелай достигнутого.
Тут keep<some_base, some_derived1, some_derived2> Хранит указатель на базовый класс в поле m_base , Ведь нет гарантий, что адреса в указателе при разных emplace<some_derived_xxx>() будут одинаковыми. Например при множественном наследовании от других классов. Кроме того при (m_base == nullptr) мы точно знаем, что объект keep пуст.
Если надо в keep создать сперва базовый класс, а потом пересоздать один из наследников, но свойства базового сохранить, то я думаю стоит эти свойства объединить в отдельную структуру. Чтобы можно было их засэйвить (скопировать или мувнуть) в локальную переменную и потом пробросить в конструктор наследника через emplace<xd>(v_params).
И напоследок: Если у целевых классов есть нетривиальный деструктор, то в базовом деструктор должен быть виртуальным.
M>Таким образом я хочу динамически изменить класс объекта сохранив его в той же области памяти. M>Или может можно как-то поменять v-table у объекта и он станет объектом другого класса ?
Т.е. нужен юнион, объекты внутри которого хранятся по одному адресу?
Одна опция — замутить свой variant. Ну или взять кусок реализации в libstdc++, которая вроде этому условию удовлетворяет.
M>>Таким образом я хочу динамически изменить класс объекта сохранив его в той же области памяти. M>>Или может можно как-то поменять v-table у объекта и он станет объектом другого класса ?
A>Т.е. нужен юнион, объекты внутри которого хранятся по одному адресу?
Да. Но нужно как-то поменять v-table у объекта и он станет объектом другого класса ?
A>Одна опция — замутить свой variant. Ну или взять кусок реализации в libstdc++, которая вроде этому условию удовлетворяет.
Здравствуйте, 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/////////7class 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/////////7union 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/////////7int main()
{
//создаем общий функционал Anew(&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;
}