Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Стандарт гарантирует (3.9) возможность побайтного копирования только для POD-типов. В остальных случаях гарантий, что в результате побайтного копирования будет получен эквивалентный или даже валидный объект, нет.
ПК>Например, можно представить себе (экзотичную, но допустимую с точки зрения стандарта) реализацию виртуальных функций в виде соответствия <адрес объекта> -> <VMT>, которая при перемещении объектов полиморфных классов побайтным копированием работать не будет. Также можно пофантазировать и на тему объектов, хранящихся в памяти не непрерывно...
Т.е. не написано что "memmove" к не POD типам применять нельзя. Правильно?
"Также можно пофантазировать и на тему объектов, хранящихся в памяти не непрерывно..."
Давай рассуждать здраво....
Наличие оператора new placement гарантирует что объект выделится а) непрерывным куском и б) займет sizeof(T).
"<адрес объекта> -> <VMT>" теоретически возможно, но практически не реально совсем.
Если так будет то все библиотеки C++ object persistence / memory mapping улетают в трубу.
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, Павел Кузнецов, Вы писали:
ПК>>Стандарт гарантирует (3.9) возможность побайтного копирования только для POD-типов. В остальных случаях гарантий, что в результате побайтного копирования будет получен эквивалентный или даже валидный объект, нет.
ПК>>Например, можно представить себе (экзотичную, но допустимую с точки зрения стандарта) реализацию виртуальных функций в виде соответствия <адрес объекта> -> <VMT>, которая при перемещении объектов полиморфных классов побайтным копированием работать не будет. Также можно пофантазировать и на тему объектов, хранящихся в памяти не непрерывно...
CS>Т.е. не написано что "memmove" к не POD типам применять нельзя. Правильно?
Нет, не написано. Тем не менее вышепроцитированное именно означает, что "memmove к не POD типам применять нельзя". Точнее, применять то ты его можешь сколько угодно, но вот только ожидать, что таки способом можно скопировать не-POD объект из одного места памяти в другое не приходится.
CS>"Также можно пофантазировать и на тему объектов, хранящихся в памяти не непрерывно..."
CS>Давай рассуждать здраво.... CS>Наличие оператора new placement гарантирует что объект выделится а) непрерывным куском
Непрерывнум куском "сырой" памяти — да, гарантирует.
CS>б) займет sizeof(T).
Нет, разумеется. Ничего такого не гарантируется. Гарантируется, что объект займет не менее чем 'sizeof(T)'. За примерами далеко ходить не надо — динамически выделяемые массивы занимают больше, чем 'sizeof' соответствующего массивного типа.
CS>"<адрес объекта> -> <VMT>" теоретически возможно, но практически не реально совсем. CS>Если так будет то все библиотеки C++ object persistence / memory mapping улетают в трубу.
Почему улетают? Во-первых, совсем не обязательно все. Во-вторых, это говорит только о том, что не существует портабельных библиотек, но запросто могут существовать кросс-платформенные.
Здравствуйте, Андрей Тарасевич, Вы писали:
ПК>>>Стандарт гарантирует (3.9) возможность побайтного копирования только для POD-типов. В остальных случаях гарантий, что в результате побайтного копирования будет получен эквивалентный или даже валидный объект, нет.
CS>>Т.е. не написано что "memmove" к не POD типам применять нельзя. Правильно?
АТ>Нет, не написано. Тем не менее вышепроцитированное именно означает, что "memmove к не POD типам применять нельзя". Точнее, применять то ты его можешь сколько угодно, но вот только ожидать, что таки способом можно скопировать не-POD объект из одного места памяти в другое не приходится.
Что значит "нельзя"? Не гарантируется стандартом, да. Но не нельзя. Есть же разница?
Господа, я не призываю двигать все и вся.
Я утверждаю следущие — если ты до конца отдаешь себе отчет что происходит в данном конкретном случае то можно.
С++ это язык для ответсвенных людей. Это не для чайников как некоторые другие языки.
Можно например сделать в C руками vtbl и объект и скормить это безобразие в C++.
И куча людей тебе скажет спасибо! И книжек море напишет.
Я говорю про принципы на которых построены и держатся COM и CORBA.
Скажите тогда свое "нельзя" авторам и пользователям данных технологий.
CS>>Наличие оператора new placement гарантирует что объект выделится а) непрерывным куском АТ>Непрерывнум куском "сырой" памяти — да, гарантирует. CS>>б) займет sizeof(T). АТ>Нет, разумеется. Ничего такого не гарантируется. Гарантируется, что объект займет не менее чем 'sizeof(T)'. За примерами далеко ходить не надо — динамически выделяемые массивы занимают больше, чем 'sizeof' соответствующего массивного типа.
Пардон, но именно гарантируется sizeof(T)/
Только если ты специально опишешь свою версию функции new то тебе удастся под объект аллоцировать больше.
По умолчанию же у тебя есть две опции применения оператора new (и две стандартные имплементации функций new):
new typename
new(location) typename.
Как минимум вторая стандартная форма гарантируют что объект "начнется" по адресу location и не выйдет за
((typename*)location + 1).
Здравствуйте, Bell, Вы писали:
1>>Тогда получается что вектор нельзя инстанировать ничем иным кроме "старых добрых простых"типов.(POD) B>Вектор может использовать специализированные алгоритмы (в том числе и с использованием memcpy/memmove) для POD-типов. Для не-POD он должен выполнять поэлементное копирование. Если у тебя vector<не-POD-тип>::insert использует memmove, то это очень странно. Было бы интересно узнать — что за компилятор, и что за реализация STL.
Есть предположение, что это идет вызов конструктора копирования. У него ведь он в классе не определен, т. е. используется конструктор копирования по-умолчанию.
c-smile,
> АТ> Нет, не написано. Тем не менее вышепроцитированное именно означает, что "memmove к не POD типам применять нельзя". Точнее, применять то ты его можешь сколько угодно, но вот только ожидать, что таки способом можно скопировать не-POD объект из одного места памяти в другое не приходится.
> Что значит "нельзя"? Не гарантируется стандартом, да. Но не нельзя. Есть же разница?
Можно еще точнее: приводит к неопределенному поведению. В терминах стандарта это одно и то же, что и "нельзя".
Переходя же в область практических рассуждений, согласен: иногда можно и нужно. Понимая, что хак, что с другим компилятором или со следующей версией этого может перестать работать и т.п. В общем, как ты верно говоришь: "если ты до конца отдаешь себе отчет что происходит в данном конкретном случае".
> CS>>б) займет sizeof(T).
> АТ> Нет, разумеется. Ничего такого не гарантируется. Гарантируется, что объект займет не менее чем 'sizeof(T)'. За примерами далеко ходить не надо — динамически выделяемые массивы занимают больше, чем 'sizeof' соответствующего массивного типа.
> Пардон, но именно гарантируется sizeof(T)/
> Только если ты специально опишешь свою версию функции new то тебе удастся под объект аллоцировать больше.
Уточняем:
5.3.4/10 A new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array.
> По умолчанию же у тебя есть две опции применения оператора new (и две стандартные имплементации функций new): > > new typename > new(location) typename. > > Как минимум вторая стандартная форма гарантируют что объект "начнется" по адресу location и не выйдет за > ((typename*)location + 1).
В случае массивов все сложнее (читай "хуже"):
[Example:
— new T results in a call of operator new(sizeof(T)),
— new(2,f) T results in a call of operator new(sizeof(T),2,f),
— new T[5] results in a call of operator new[](sizeof(T)*5+x), and
— new(2,f) T[5] results in a call of operator new[](sizeof(T)*5+y,2,f).
Here, x and y are non-negative unspecified values representing array allocation overhead; the result of the new-expression will be offset by this amount from the value returned by operator new[]. This overhead may be applied in all array new-expressions, including those referencing the library function operator new[](std::size_t, void*) and other placement allocation functions. The amount of overhead may vary from one invocation of new to another.]
Возвращаясь к фантазиям на тему не непрерывных объектов, можно заметить, что реализация может легко различать объекты, созданные через placement new, и через "обычный" new. Мотивы разработчиков такого чуда оставляем за кадром
Posted via RSDN NNTP Server 1.9 delta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
#pragma pack(push,1)
class X {
public:
int i;
void f();
};
#pragma pack(pop)
то sizeof(X)==4, т.е. выделяется память только для int i;
Если же написать
#pragma pack(push,1)
class X {
public:
int i;
virtual void f();
};
#pragma pack(pop)
то sizeof(X)==8! т.е. также выделяется память и для указателя на vftable
Такой код дает тоже 8 байт:
class Z {
};
#pragma pack(push,1)
class X: virtual public Z {
public:
int i;
void f();
};
#pragma pack(pop)
А такой -- 12:
class X: virtual public Z {
public:
int i;
virtual void f();
};
Таким образом мы делаем вывод, что VC++ 7.1 использует описанную мной выше структуру. Теперь можно пробовать копировать класс с вирутальной функцией и виртуальным предком.
class Z {
};
#pragma pack(push,1)
class X: public virtual Z {
public:
int a;
virtual void f();
};
#pragma pack(pop)
void X::f() {
printf("X::f()");
}
void main() {
X x1;
int sz=sizeof(x1);
char ch1[sizeof(X)];
memcpy(ch1,&x1,sizeof(X));
X *x2=(X *)ch1;
x2->f();
}
все прекрасно работает! т.е. на экран выводится ожидаемое X::f().
N.B. нормальные деструкторы я делать не стал, важно было проверить правильность идеи.
ВЫВОД. Объекты с виртуальными функциями и виртуальными предками в VC++ 7.1 копировать с помощью memcpy() etc. можно!
Здравствуйте, slegkapjan, Вы писали:
S>ВЫВОД. Объекты с виртуальными функциями и виртуальными предками в VC++ 7.1 копировать с помощью memcpy() etc. можно!
На основе практического экперимента, да еще и на совершенно не показательных примерах, такого вывода сделать, разумеется, нельзя. Разве что в юмористическом плане. Это что-то из области "безногий таракан не слышит" из диссертации ВИЧ.
АТ>На основе практического экперимента, да еще и на совершенно не показательных примерах, такого вывода сделать, разумеется, нельзя.
Т.е. вы считаете, что "практический эксперимент" не может приводить к каким-то выводам?
И какие эксперименты вы считаете "показательными"?
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Уточняем: ПК>
5.3.4/10 A new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array.
Ну а я о чем говорил? (bold emphasizing is mine)
void *someptr;
выражение:
new(someptr) T;
вызовет placement функцию ::operator new с параметрами
::operator new( sizeof(T), someptr )
Возвращаясь же к практическим соображениям и резюмируя вышесказанное (с глубоким почтением ко всем участникам) еще раз рискну заметить:
Не виртуальные классы (т.е. "простые" но уже не POD) "двигать" достаточно безопасно.
Экзепмпляры виртуальных классов "двигать" в принципе можно, но отдавая себе отчет что такое решение потенциально не portable т.е.зависит от конкретного компилятора. Хотя все известные мне компиляторы и safe в этом плане, проверять все равно надо. Условно говоря такое решение — на совести разработчика и мотвационных установок проекта.
И лирика: на самом деле такие вот возможности (не обязательно эти) и делают C++ тем чем он есть — платформой для создания эффективных програм. С++ ффективный и универсальный но не "дуракоустойчивый". Что есть то есть.
CS>И лирика: на самом деле такие вот возможности (не обязательно эти) и делают C++ тем чем он есть — платформой для создания эффективных програм. С++ ффективный и универсальный но не "дуракоустойчивый". Что есть то есть.
5 баллов.
Здравствуйте, 1234, Вы писали:
1>То объекты этого класса, если я неошибаюсь, можно двигать в памяти как душе угодно(разумеется в валидных областях памяти). memmove, memcpy and e.t.c.
B>>Думаю, не нужно объяснять, что произойдет при вызове set для объекта, который был перемещен после создания с помощью memmove ... 1>Данный пример иллюстрирует проблему появления висячей ссылки(dead reference), это произойдёт так же в случае если на объект имеются ссылки где-то вне объекта. Эти вещи нужно учитывать, точно так же как если куда-то передаётся адрес какого-то объекта, то нужно гарантировать определённое время жизни этого объекта, что бы непроизошло обращение к уже удалённому объекту.
CS>Экзепмпляры виртуальных классов "двигать" в принципе можно, но отдавая себе отчет что такое решение потенциально не portable т.е.зависит от конкретного компилятора. Хотя все известные мне компиляторы и safe в этом
Борландовский компилятор 5.х (возможно, это был какой-то g++ но по-моему, все ж таки борланд 5.5) для определения положения подобъекта виртуального базового класса использует НЕПОСРЕДСТВЕННЫЙ адрес. Копируй наздоровье свои объекты как хочешь, но не говори, что это делать можно. Можно писать и int i = 0; i = i++ + ++i; если тебе так охота. Только опять же, не надо говорить, что так можно, "потому что ты знаешь, что делаешь".
Of course, the code must be complete enough to compile and link.
Что такое "НЕПОСРЕДСТВЕННЫЙ адрес" и чем оно отличается от просто "адрес"?
И вообще я уже запарился повторять одну и ту же фразу.
Давай этот раз я её выделю особо "КОГДА ОТДАЕШЬ СЕБЕ ОТЧЕТ".
Если же ты используешь в быту virtual inheritnace with virtual base classes,
что в общем и целом разрешено в C++ но в design guides не поощрается,
то как минимум ты обязан себе отдавать отчет в том что происходит.
Для чего-то же ты вставил этот :public virtual base?
Т.е. предполагается что ты уже задумываешься над memory layout, правильно?
А если при том ты хочешь "двигать" такие классы то ты должен знать свой(и) компилятор(ы)
Например для VC++ вот это полезно знать:
For the so-called Microsoft C++ Object Mapping, the object layout
algorithm for a given class is approximately:
0. start with an empty struct;
1. add a virtual function table pointer (vfptr) if this class
has new virtual functions and does not inherit a vfptr from the
non-virtually-inherited parts of some direct non-virtual base class;
2. add a virtual base displacement table pointer (vbptr) if this
class has virtual bases and does not inherit a vbptr from the
non-virtually-inherited parts of some direct non-virtual base class;
3. add an embedded instance of the non-virtually-inherited parts of each
direct non-virtual base class, in declaration order;
4. add the non-static data members in declaration order;
5. add an embedded instance of the non-virtually-inherited parts of each
direct or indirect virtual base class, (if I recall correctly) in the
order found given a depth-first left-to-right preorder traversal of
the base class graph.
(Intentionally avoiding discussion of the details of bitfields, padding
for alignment, virtual function tables, virtual base displacement tables,
"adjuster thunks", 0-sized bases, transitive virtual bases, the dreaded
"construction displacement" mechanism, etc., etc.)
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, Павел Кузнецов, Вы писали:
ПК>>Уточняем: ПК>>
5.3.4/10 A new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array.
CS>Ну а я о чем говорил? (bold emphasizing is mine)
CS>
CS>void *someptr;
CS>выражение:
CS>new(someptr) T;
CS>вызовет placement функцию ::operator new с параметрами
CS>::operator new( sizeof(T), someptr )
CS>
CS>Возвращаясь же к практическим соображениям и резюмируя вышесказанное (с глубоким почтением ко всем участникам) еще раз рискну заметить:
CS>Не виртуальные классы (т.е. "простые" но уже не POD) "двигать" достаточно безопасно. CS>Экзепмпляры виртуальных классов "двигать" в принципе можно, но отдавая себе отчет что такое решение потенциально не portable т.е.зависит от конкретного компилятора. Хотя все известные мне компиляторы и safe в этом плане, проверять все равно надо. Условно говоря такое решение — на совести разработчика и мотвационных установок проекта.
CS>И лирика: на самом деле такие вот возможности (не обязательно эти) и делают C++ тем чем он есть — платформой для создания эффективных програм. С++ ффективный и универсальный но не "дуракоустойчивый". Что есть то есть. что верно — то верно!
Благодарю всех за дискуссию, внимательно следил за развитием топика.
К сожалению, раc стандарт гласит что можно "двигать" только POD типы, то совершенно спокойным быть нельзя даже если класс проще молотка, т.к. существует вероятность что на каком-нибудь компиляторе, где-то когда-то этот метод может перестать работать.
Однако, так же, не теряя здравого смысла, и учитывая ВСЁ сказанное выше(и втом числе последнее резюмирующее собщение от c-smile), в целях оптимизации(и только ради неё!) принял решение, что это допустимо для класса у которого нет ни виртуального наследования, ни виртуальных функций-членов(и даже деструктор невиртуальный хоть это и неочень хорошо).
На том и порешил.
Всем спасибо.
Если у кого будет что добавить по теме — welcome!
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, Lorenzo_LAMAS, Вы писали:
CS>Что такое "НЕПОСРЕДСТВЕННЫЙ адрес" и чем оно отличается от просто "адрес"?
Я хотел подчеркнуть, что хранится не указатель на какую-то часть таблицы, с помощью котрого и которой найдется подобъект, а адрес этого подобъекта.
CS>Например для VC++ вот это полезно знать:
А вот за ссылку спасибо.
Of course, the code must be complete enough to compile and link.
Здравствуйте, 1234, Вы писали:
1>Уточню ещё раз вторую часть вопроса: в чём именно проблема с указателем на таблицу виртуальных функций ?
С указателем на таблицу виртуальных функций проблем нет никаких. Могут быть проблемы при наличии виртуального наследования.
1>Я никак немогу понять — с самого начала хранятся два указателя — на vptable и на vftable.
Если нет виртуального наследования, то указатель только один -- на глобально определённую (одну на все экземпляры класса) таблицу виртуальных функций.
1>Сами таблицы находятся где ? Предположим мы передвинули класс, вмести с остальными данными переехали и эти два указателя, но разве их значения поменялись ?
Соответственно, при наличии отсутствия виртуального наследования, ответ НЕТ.
1>Или возможно, Андрей Тарасевич имел в виду своим примером то, что сами таблицы, на которые ссылаются указатели, находятся где-то среди данных класса ?
Нет.
1>(тогда я вообще непонимаю — где ???) и после сдвига изменяются так же адреса этих таблиц ???
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>c-smile,
>> АШ> причем здесь STL, перемещение (memmove) и копирование (memcpy) разрешено только для POD-типов и это регулирует стандарт — так что тень на плетень не надо наводить и так ночь на дворе.
>> Где это такое написано? Ссылку даем, да?
ПК>Стандарт гарантирует (3.9) возможность побайтного копирования только для POD-типов. В остальных случаях гарантий, что в результате побайтного копирования будет получен эквивалентный или даже валидный объект, нет.
Но это совсем не значит, что в конкретной реализации для конкретного класса это не будет работать. Просто, будет платформенно-зависимый код.
ПК>Например, можно представить себе (экзотичную, но допустимую с точки зрения стандарта) реализацию виртуальных функций в виде соответствия <адрес объекта> -> <VMT>, которая при перемещении объектов полиморфных классов побайтным копированием работать не будет. Также можно пофантазировать и на тему объектов, хранящихся в памяти не непрерывно...
Нафантазировать можно много чего. Есть всё-таки наиболее распространённые реализации.