Здравствуйте, Vain, Вы писали:
V>Здравствуйте, A13x, Вы писали:
А>>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>>... A>>ужос. A>>Нафига вообще менять VPTR?? A>>Здесь же просится паттерн стратегия: V>Что-то ваш паттерн стратегия напоминает паттерн Pimpl
pimpl да не тот
В моем понимание паттерна "стратегия" в данном конкретном случае использование этого паттерна не обязательно предполагает перенос всей реализации класса в стратегию, т.е. мы не обязательно должны менять поведение класса целиком и полностью.
Т.е. у класса может быть ряд методов не нуждающихся в смене реализации + к тому ряд методов предполагающих использование стратегии могут предлагать реализацию по умолчанию, если стратегия не задана.
Вот утрированный пример (не из жизни):
class ResultSet {
private:
Strategy s;
DataProvider p;
public:
Row getRow(int index) {
if (s != null) {
// иное поведение если есть стратегия получения (например из кэша)return s->getRow(p, index);
}
return rowCollection->fetchRow(index);
}
void applySecurityConstraint(SecurityConstraint c) {
// а вот этот метод, положим, немыслим без стратегии
ASSERT_VALID(s);
s->applySecurityConstraint(p, c);
}
RowMetadata getRowMetadata() {
// а для этого метода нет какой-то особой стратегииreturn p->getRowMetadata();
}
}
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
KO>>Здравствуйте, rg45, Вы писали:
R>>>Здравствуйте, k.o., Вы писали:
R>>>>>Правильно. Таким образом, после "подмены vptr" объект использовать не надо, ч.т.д.
KO>>>>Смотря что понимать под выделенным. Если что-то вроде твоего примера, то не надо, если что-то другое, то вполне можно:
R>>>Ну это уже несколько другой use case, чем в исходном посте. Изначальная идея — в существующем полиморфном объекте подменить реализацию вместе с таблицей виртуальных функций и продолжать использовать тот же объект.
KO>>Я такого в исходном сообщении не увидел. Мне показалось, что KO>>
KO>>Interface intf;
KO>>недвусмысленно намекает на то, что работать мы будем через интерфейс. Разумеется, тот код требует некоторого допиливания, хотя бы потому, что он не компилируется, но ТС сразу написал, что это только идея. Я, впрочем, не буду спорить, что говорить о замене vptr несколько некорректно, т.к. мы просто используем память существующего объекта для создания нового.
R>Ну, видимо, "допилили" мы этот код по-разному. Я это сделал так: R>
R>Interface* intf;
R>//...
R>intf = new (intf) T(); // происходит подмена VPTR
R>
R>Но ты уже вроде бы признал, что так делать некорректно.
Ну, я думал, что сделать ещё один небольшой допил будет несложно:
Хотя, при некоторых условиях будет работать даже тот код, который ты только что написал. Не забывай, что в том примере, который ты предлагал обсудить, не было обновления intf после создания нового объекта.
R>А как еще можно "допилить" это пример? Если "мы просто используем память существующего объекта для создания нового", то какие типы могут иметь существующий и новый объекты?
Любые типы для которых подходит размер и выравнивание используемого участка памяти, и для которых можно не вызывать деструктор (или добавляем вызов оного перед new).
Здравствуйте, k.o., Вы писали:
R>>Ну допустим. Таким образом, пришли к тому, что для использования такого приема требуется чтобы: KO>Не "допустим", а таково требование стандарта, можно подумать, кто-то умрёт если ты прямо напишешь что ошибся.
Признаю, ошибся.
ТКС>>UB. Вообще, хотя бы деструктор предыдущей реализации надо вызвать перед placement new, ну и позаботиться о том, чтобы хватило памяти (sizeof(intf) >= sizeof любой из реализаций) под все реализации. И это все равно не гарантирует отсутствия проблем при оптимизации.
KO>Деструктор, если он тривиальный, вызывать необязательно, UB будет только если размеры или выравние создаваемых объектов не соответсвуют используемой памяти.
KO>Если да, то я нигде не призывал, отказаться от вызова деструкторов, а всего лишь выразил сомнение в том, что обязательно будет UB.
А вот здесь я бы акцент все-таки поставил бы по-другому. UB не будет только в том случае, если есть гарантия того, что ни в одном из классов иерархии нет нетривиальных деструкторов. Вряд ли такое требование можно назвать типичным для иерархий с абстрактными классами во главе. В исходном сообщении такого ограничения также не накладывается — строчки "//реализация..." говорят о том, что в производных классах могут присутстовать члены с нетривиальными деструкторами. Видимо, Тот-кто-сидит-в-пруду, когда говорил о необходимости вызова деструкторов, также в качестве общего случая рассматривал (вполне обоснованно) ситуацию, когда классы имеют нетривиальные деструкторы.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Caracrist, Вы писали:
C>Здравствуйте, k.o., Вы писали:
KO>>Разумеется, тот код требует некоторого допиливания, хотя бы потому, что он не компилируется,
C>http://www.rsdn.ru/forum/cpp/4221864.aspx
KO>Хотя, при некоторых условиях будет работать даже тот код, который ты только что написал. Не забывай, что в том примере, который ты предлагал обсудить, не было обновления intf после создания нового объекта.
Ну можно еще пофантазировать, как тебе такой вариант:
Interface& intf;
//...new (&intf) T(); // происходит подмена VPTR
Этот вариант разбомбить тоже не сложно.
Но вот чем бы не хотелось заниматься, так это соревноваться в том, у кого правильней работает фантазия. В конце-концов пусть ТС предоставит компилируемый (хотя бы) пример, тогда уже и будем обсуждать.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
R>>>Ну допустим. Таким образом, пришли к тому, что для использования такого приема требуется чтобы: KO>>Не "допустим", а таково требование стандарта, можно подумать, кто-то умрёт если ты прямо напишешь что ошибся.
R>Признаю, ошибся.
ТКС>>>UB. Вообще, хотя бы деструктор предыдущей реализации надо вызвать перед placement new, ну и позаботиться о том, чтобы хватило памяти (sizeof(intf) >= sizeof любой из реализаций) под все реализации. И это все равно не гарантирует отсутствия проблем при оптимизации.
KO>>Деструктор, если он тривиальный, вызывать необязательно, UB будет только если размеры или выравние создаваемых объектов не соответсвуют используемой памяти.
KO>>Если да, то я нигде не призывал, отказаться от вызова деструкторов, а всего лишь выразил сомнение в том, что обязательно будет UB.
R>А вот здесь я бы акцент все-таки поставил бы по-другому. UB не будет только в том случае, если есть гарантия того, что ни в одном из классов иерархии нет нетривиальных деструкторов.
Здесь ты тоже немного ошибаешься, UB не будет если "есть гарантия того, что ни в одном из классов иерархии нет нетривиальных деструкторов", или после "смены" vptr мы не вызываем деструктор старого объекта и наша программа не "зависит" (depends что бы это ни значило) от побочных эффектов работы деструктора.
R>Вряд ли такое требование можно назвать типичным для иерархий с абстрактными классами во главе. В исходном сообщении такого ограничения также не накладывается — строчки "//реализация..." говорят о том, что в производных классах могут присутстовать члены с нетривиальными деструкторами. Видимо, Тот-кто-сидит-в-пруду, когда говорил о необходимости вызова деструкторов, также в качестве общего случая рассматривал (вполне обоснованно) ситуацию, когда классы имеют нетривиальные деструкторы.
Строчки "//реализация...", если посмотреть внимательнее, находятся внутри определений метода someFun, поэтому никаких дополнительных членов там нет.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
R>Но вот чем бы не хотелось заниматься, так это соревноваться в том, у кого правильней работает фантазия. В конце-концов пусть ТС предоставит компилируемый (хотя бы) пример, тогда уже и будем обсуждать.
Здравствуйте, Caracrist, Вы писали:
А>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>Собственно вопрос в топике.
Ну вот, другое дело. Только вряд ли теперь "замена VPTR" является удачным названием для такой реализации. Это просто многократное использование буфера в качестве хранилища для объектов.
Что не очень хорошо — это небезопасность с точки зрения исключений:
Что если во время конструирования нового объекта возникает исключение? Старый объект разрушен, новый не создан, буфер содержит мусор, объект-владелец оказывается в несогласованном состоянии. Как решить эту проблему при такой реализации я не представляю.
И еще, в качестве аргумента помещающего new я бы использовал не адрес массива, а просто массив:
intf = new (buff) T();
Здесь массив неявно преобразуется к указателю на первый элемент... и короче и спокойнее как-то, хотя не принципиально, наверное.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, k.o., Вы писали:
KO>Трюк в том, чтобы не использовать ссылки и указатели на старый объект. Поскольку мы работаем с объектами через указатели на интерфейс дальнейшее можно не учитывать. В самом деле, чем, по твоему это отличается от KO>
KO>Interface * i = 0;
KO>void * storage = malloc(<size of storage for any implementation>);
KO>Impl1 * impl1 = new (storage) Impl1;
KO>i = impl1;
i->>foo();
i->>~Interface();
KO>Impl2 * impl2 = new (storage) Impl2;
KO>i = impl2;
i->>foo();
i->>~Interface();
KO>free(storage);
KO>
Это отличается заказом памяти. Собственно разница состоит в "malloc(<size of storage for any implementation>);"
А занимаясь такими вещами, мы делаем шаг в сторону С-- (здесь) или же в сторону Майкросовтовского CoCreateInstance.
Чем, собственно, не устраивает классический подход :
class User { // потребительpublic:
Interface* intf;
template <class T> changeIntfRealization() {
delete intf;
intf = new T;
}
};
Здравствуйте, Caracrist, Вы писали:
А>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>Собственно вопрос в топике.
А вот теперь интересный вопрос: а ради чего сыр-бор? Вот простая, понятная, компактная, безопасная с точки зрения исключений реализация:
using boost::shared_ptr;
class User { // потребительpublic:
shared_ptr<Interface> intf;
User() {/*Не нужен*/}
~User(){/*Не нужен*/}
template <class T>
void changeIntfRealization() {
intf.reset(new T());
}
};
А какие преимущества имеет твоя реализация?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, k.o., Вы писали:
BFE>Это отличается заказом памяти. Собственно разница состоит в "malloc(<size of storage for any implementation>);"
И что такого волшебного в malloc? Можно использовать и new char[<size of storage for any implementation>] или вообще aligned_storage<(size of storage for any implementation)>::type storage;
BFE>А занимаясь такими вещами, мы делаем шаг в сторону С-- (здесь) или же в сторону Майкросовтовского CoCreateInstance.
Какими такими?
BFE>Чем, собственно, не устраивает классический подход :
Позволю себе процитировать ТС:
Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, Caracrist, Вы писали:
А>>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>>Собственно вопрос в топике.
R>Ну вот, другое дело. Только вряд ли теперь "замена VPTR" является удачным названием для такой реализации. Это просто многократное использование буфера в качестве хранилища для объектов.
R>Что не очень хорошо — это небезопасность с точки зрения исключений: R>
R>Что если во время конструирования нового объекта возникает исключение? Старый объект разрушен, новый не создан, буфер содержит мусор, объект-владелец оказывается в несогласованном состоянии. Как решить эту проблему при такой реализации я не представляю.
Не вижу тут вообще ни какой проблемы
void Release() {
if (intf) intf->~Interface();
intf = 0;
}
R>И еще, в качестве аргумента помещающего new я бы использовал не адрес массива, а просто массив: R>
R>intf = new (buff) T();
R>
R>Здесь массив неявно преобразуется к указателю на первый элемент... и короче и спокойнее как-то, хотя не принципиально, наверное.
на самом деле тут очепятка
Здравствуйте, Caracrist, Вы писали:
C>Здравствуйте, rg45, Вы писали:
R>>А какие преимущества имеет твоя реализация? C>моя, это доработка идеи топикастера... за преимуществами в нему
R>>Что если во время конструирования нового объекта возникает исключение? Старый объект разрушен, новый не создан, буфер содержит мусор, объект-владелец оказывается в несогласованном состоянии. Как решить эту проблему при такой реализации я не представляю. C>Не вижу тут вообще ни какой проблемы C>
Такая мера, конечно, отчасти решает проблему. Но, тем не менее, работать с объектами проще, если обеспечена транзакционность операций над ними — операция либо завершается успешно, либо, в случае возникновения ошибки, состояние объекта не изменяется. Клиентскому коду тогда остается меньше возможных ситуаций, которые он должен уметь обрабатывать.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, Caracrist, Вы писали:
А>>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>>Собственно вопрос в топике.
R>А вот теперь интересный вопрос: а ради чего сыр-бор? Вот простая, понятная, компактная, безопасная с точки зрения исключений реализация:
Вот не менее простая и безопасная реализация:
Interface*
// а также
//shared_ptr<Interface>
//auto_ptr<Interface>
После уже второго такого обсуждения о хаке виртуальной таблицы стало интересно — а не наткнутся ли любители переписывать таблицы виртуальных методов на грабли в виде девиртуализации проведенной оптимизирующем компилятором?
Знаю, что современные компиляторы в ряде случаев могут использовать т.н. девиртуализацию — прямой вызов функции (или даже inline подстановка) в месте вызова функции.
Уже лень если честно писать proof-of-concept примеры, сериалы ждут.
Кажется, что если такая проблема существует описание классов в стиле C
Здравствуйте, Caracrist, Вы писали:
R>>А вот теперь интересный вопрос: а ради чего сыр-бор? Вот простая, понятная, компактная, безопасная с точки зрения исключений реализация: C>Вот не менее простая и безопасная реализация: C>
C>Interface*
C>// а также
C>//shared_ptr<Interface>
C>//auto_ptr<Interface>
C>
C>
Ну c shared_ptr действительно все хорошо и просто, именно этот вариант я и описал. А вот с оставшимися двумя вариантами — голым указателем и auto_ptr — не все так просто и однозначно, как может показаться на первый взгляд. Достаточно вспомнить о генерируемых по умолчанию конструкторе копии и копирующем операторе присваивания. Поведение этих функций членов, сгенерированных компилятором, не всегда будет желаемым, а порой может просто приводить к ошибкам.
--
Справедливость выше закона. А человечность выше справедливости.