Здравствуйте, maks1180, Вы писали:
R>>Согласно стандарту языка, использование одной и той же области памяти для размещения двух разных объектов порождает неопределенное поведение. Нельзя так делать.
M>Даже если первый объект больше не будет использован после создания второго ?
Ты знаешь, наверное, я был не совсем прав, все-таки, на счет неопределенного поведения (смайлик от Сигмы заставил открыть стандарт). И оказалось, не все так однозначно, как мне казалось. В частности, в стандарте прямо говорится, что программа может завершать время жизни без вызова деструктора и, само по себе это не влечет неопределенного поведения:
A program may end the lifetime of an object of class type without invoking the destructor, by reusing or releasing the storage as described above.
Но надо понимать, что деструктор объекта при этом не вызывается, а вот это уже может порождать неопределенное поведение. В этом же пунтке сказано вот что:
In this case, the destructor is not implicitly invoked and any program that depends on the side effects produced by the destructor has undefined behavior.
То есть, не UB в чистом виде, но программирование на грани фола, что называется. Без особой необходимости пускаться в такие авантюры лично я бы не стал.
--
Не можешь достичь желаемого — пожелай достигнутого.
R>Но надо понимать, что деструктор объекта при этом не вызывается, а вот это уже может порождать неопределенное поведение. В этом же пунтке сказано вот что:
R>
R>In this case, the destructor is not implicitly invoked and any program that depends on the side effects produced by the destructor has undefined behavior.
Здравствуйте, 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 ?
Согласно стандарту языка, использование одной и той же области памяти для размещения двух разных объектов порождает неопределенное поведение. Нельзя так делать.
Тут keep<some_base, some_derived1, some_derived2> Хранит указатель на базовый класс в поле m_base , Ведь нет гарантий, что адреса в указателе при разных emplace<some_derived_xxx>() будут одинаковыми. Например при множественном наследовании от других классов. Кроме того при (m_base == nullptr) мы точно знаем, что объект keep пуст.
Если надо в keep создать сперва базовый класс, а потом пересоздать один из наследников, но свойства базового сохранить, то я думаю стоит эти свойства объединить в отдельную структуру. Чтобы можно было их засэйвить (скопировать или мувнуть) в локальную переменную и потом пробросить в конструктор наследника через emplace<xd>(v_params).
И напоследок: Если у целевых классов есть нетривиальный деструктор, то в базовом деструктор должен быть виртуальным.
Здравствуйте, 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;
}
Здравствуйте, maks1180, Вы писали:
M>Спасибо, но я хотел бы обойтись без std::move, тем более без двойного. M>Ведь мы же создаём объект на том же месте, или конструктор может затереть всё нулями ?
Конструктор не затрет, если он будет тривиальным и нигде не будет инициировано zero-initialization, во всех остальных случаях — затрет. Но это семечки, есть более страшная штука, касается UB по причине использования несконструированных (вне lifetime) значений, тех самых, которые не были "затерты" конструктором и остальсь от предыдущего объекта. Это практически опасная ситуация, потому что компилятор будет чудить по причине этого UB.
Допустим есть 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 тактов.
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 программа обязана обеспечить корректное завершение времени жизни объекта, тем или иным образом.
--
Не можешь достичь желаемого — пожелай достигнутого.
M>Таким образом я хочу динамически изменить класс объекта сохранив его в той же области памяти. M>Или может можно как-то поменять v-table у объекта и он станет объектом другого класса ?
Т.е. нужен юнион, объекты внутри которого хранятся по одному адресу?
Одна опция — замутить свой variant. Ну или взять кусок реализации в libstdc++, которая вроде этому условию удовлетворяет.
M>>Таким образом я хочу динамически изменить класс объекта сохранив его в той же области памяти. M>>Или может можно как-то поменять v-table у объекта и он станет объектом другого класса ?
A>Т.е. нужен юнион, объекты внутри которого хранятся по одному адресу?
Да. Но нужно как-то поменять v-table у объекта и он станет объектом другого класса ?
A>Одна опция — замутить свой variant. Ну или взять кусок реализации в libstdc++, которая вроде этому условию удовлетворяет.
Идея хорошая, только я без std::move хотел обойтись. Если Client1 наследник от ClientCommon, то не нужно дедать std::move, так как он по тому же адресу должен лежать.
Или не так ?
Спасибо, но я хотел бы обойтись без std::move, тем более без двойного.
Ведь мы же создаём объект на том же месте, или конструктор может затереть всё нулями ?
Здравствуйте, maks1180, Вы писали:
M>Да. Но нужно как-то поменять v-table у объекта и он станет объектом другого класса ?
Так нельзя. Нарушаешь стандарт на счёт времени жизни. Обязательно нужно конструктор и деструктор звать. Так что, placement new как в variant — единственный правильный путь имхо.
Здравствуйте, maks1180, Вы писали:
S>> T * ret = new (&m_data) T(std::forward<T_args>(p_args) ...);
M>Разве ret может отличатся от &m_data ?
Вроде нет. Значит можно написать return &m_data; а ret убрать.
M>Как показывает мой опыт, добавление промежуточного указателя m_base, может повлечь снижение производительности за счёт промаха кэша.
M>Идея хорошая, только я без std::move хотел обойтись. Если Client1 наследник от ClientCommon, то не нужно дедать std::move, так как он по тому же адресу должен лежать. M>Или не так ?
1. Нет гарантий, что это так
2. Нет гарантий, что данные не будут перезатёрты
//
Можно хранить параметры отдельно, и добавить указатель на них в ClientCommon, если не хочешь их мувать.
//
Я понимаю, что ты хочешь досоздать Client1 из ClientCommon, а cpp этого нет.
Но тогда можно сделать pimpl.
Здравствуйте, vopl, Вы писали:
V>Здравствуйте, maks1180, Вы писали:
M>>Спасибо, но я хотел бы обойтись без std::move, тем более без двойного. M>>Ведь мы же создаём объект на том же месте, или конструктор может затереть всё нулями ?
V>Конструктор не затрет, если он будет тривиальным и нигде не будет инициировано zero-initialization, во всех остальных случаях — затрет. Но это семечки, есть более страшная штука, касается UB по причине использования несконструированных (вне lifetime) значений, тех самых, которые не были "затерты" конструктором и остальсь от предыдущего объекта. Это практически опасная ситуация, потому что компилятор будет чудить по причине этого UB.
V>Другими словами — не делай так.
Здравствуйте, Sm0ke, Вы писали:
S>2. Нет гарантий, что данные не будут перезатёрты
ага https://godbolt.org/z/e5M49nc7P
но с вариантами всё будет работать. Там создаётся новый вариант и только потом вызывается конструктор перемещения. В общем, два перемещения. Если вызывать emplace то всё будет печально.