Подозреваю, что вопрос многим уже набил оскомину, но, к сожалению, не смог найти ответа. Дело касается так называемого глубокого копирования и реализации необходимых для этого конструкторов копирования и операторов присваивания. Все примеры, которые я смог обнаружить состоят из одного класса, у которого имеется ресурс в виде, допустим, массива int'ов, под который выделяется память при создании объекта или выделяется и копируется при копировании объекта. Тут вроде бы все понятно, тем не менее проблема у меня возникает при работе с несколькими классами, когда один из них аггрегирован в другой.
Собственно вопрос заключается в том как проинициализировать EmbeddedClass, который входит в MainClass, когда используется семантика кучи (т.е. объект создается в динамической памяти посредством оператора new)? Если не использовать семантику кучи и работать без указателей, то конструктор копирования вызывается и нормально работает по следующей схеме:
C>MainClass::MainClass(int nSize, int nDefaultVal)
C> : embedded(1,2,3,4,5,6, nSize, nDefaultVal)
C>
C>и получишь тот же результат
C>имелось ввиду наверное вот это: C>
C>MainClass::MainClass(int nSize, int nDefaultVal)
C> : embedded(new EmbeddedClass(nSize, nDefaultVal))
C>
C>
Да, согласен, тут явная бага, осталась от замены семантики стека на семантику кучи, а проблема была с разыменованием указателя у меня, не доходило никак почему не вызывается конструктор копирования Теперь все наконце-то встало на свои места
Ко всему ранее сказанному добавлю, что здесь (и в следующем классе) нарушен Law of the Big Three: если в классе нужен любой из трёх элементов списка [деструктор, копирующий конструктор, копирующий оператор присваивания], то, скорее всего, нужны все три.
В применении к данному классу:
{
EmbeddedClass a(3, 14);
{
EmbeddedClass b(2, 72);
// a и b владеют массивами соответсвенно из трёх и двух элементов
a = b;
// массив, владеемый объектом a, утёк
// на массив, ранее принадлежавший объекту b, указывают оба объекта a и b
}
// объект b разрушился и унёс за собой массив
// объект a указывает в никуда
}
// объект a разрушился и попытался освободить массив
// double free — неопределённое поведение
Большое спасибо всем за советы! Они мне очень помогли!
Но чем дальше в лес, тем больше дров, и у меня возникла еще пара вопросов:
1. При добавлении оператора присваивания (вопросы поместил в комментариях к коду):
EmbeddedClass& EmbeddedClass::operator =(const EmbeddedClass& rhs)
{
if (this != &rhs)
{
delete[] m_szInternalResource;
/*
^^^^ Корректно ли так писать или сперва необходимо добавить проверку на NULL, а потом уже вызывать delete[], т.е.
if (NULL == m_szInternalResource)
delete[] m_szInternalReource;
*/
m_szInternalResource = new char[m_nBufSize = rhs.m_nBufSize];
/*
Если здесь ^^^^ в качестве параметра оператору new передать размер массива = 0,
то какое поведение будет после этого? Это валидно так поступать или необходимо
проверить этот параметр перед выделением памяти, например, так:
if ((m_nBufSize = rhs.m_nBufSize) > 0)
m_szInternalResource = new char[m_nBufSize];
Тот же вопрос и про отрицательную размерность, например, -2?
*/
std::copy(rhs.m_szInternalResource, &rhs.m_szInternalResource[rhs.m_nBufSize], m_szInternalResource);
// В данном случае ^^^^, насколько я могу судить, всегда гарантируется то, что у элемента rhs.m_szInternalResource[rhs.m_nBufSize] можно взять адрес (хотя массив имеет размер на один меньше); то есть это сделано для совместимости с алгоритмами STL
}
return *this;
}
2. Если я добавляю класс наследник допустим для класса MainClass:
то при определении конструктора копирования и оператора присваивания я должен заботиться только о внутренних ресурсах наследника? Все ресурсы классов родителей будут автоматически скопированны родительскими методами и эти методы будут вызваны также автоматически?
Здравствуйте, c_plus_plus_newbie, Вы писали:
___> 1. При добавлении оператора присваивания (вопросы поместил в комментариях к коду):
[...]
___> delete[] m_szInternalResource;
delete, delete[] и C-шная free корректно работают с переданным нулевым указателем — проверка на 0 перед удалением не нужна.
[...]
___> m_szInternalResource = new char[m_nBufSize = rhs.m_nBufSize];
new может принимать 0 в качестве размера массива.
___> Тот же вопрос и про отрицательную размерность, например, -2?
Отрицательный размер будет приведён к (очень большому из-за особенности представления) беззнаковому числу и получится запрос на большое кол-во памяти. Естественно, в большинстве случаев для отрицательного размера массива будет сгенерировано исключение std::bad_alloc.
___>2. Если я добавляю класс наследник допустим для класса MainClass:
[...] ___>то при определении конструктора копирования и оператора присваивания я должен заботиться только о внутренних ресурсах наследника? Все ресурсы классов родителей будут автоматически скопированны родительскими методами и эти методы будут вызваны также автоматически?
Вызов явно написать придётся: