Еще раз про copy constructor
От: Аноним  
Дата: 16.07.10 15:02
Оценка:
Добрый день, друзья!

Подозреваю, что вопрос многим уже набил оскомину, но, к сожалению, не смог найти ответа. Дело касается так называемого глубокого копирования и реализации необходимых для этого конструкторов копирования и операторов присваивания. Все примеры, которые я смог обнаружить состоят из одного класса, у которого имеется ресурс в виде, допустим, массива int'ов, под который выделяется память при создании объекта или выделяется и копируется при копировании объекта. Тут вроде бы все понятно, тем не менее проблема у меня возникает при работе с несколькими классами, когда один из них аггрегирован в другой.

Есть класс:

/
/ EmbeddedClass.h
class EmbeddedClass
{
public:
    EmbeddedClass(void);
    EmbeddedClass(int nSize, int nDefaultValue = 0);
    EmbeddedClass(const EmbeddedClass& rhs);
    virtual ~EmbeddedClass(void);

    void Print();

protected:
    char * m_szInternalResource;
    int m_nBufSize;
};


// EmbeddedClass.cpp
#include "StdAfx.h"
#include "EmbeddedClass.h"
#include <algorithm>
#include <iostream>

EmbeddedClass::EmbeddedClass(void)
    : m_szInternalResource(NULL), m_nBufSize(0)
{}

EmbeddedClass::EmbeddedClass(int nSize, int nDefaultValue)
    : m_szInternalResource(NULL)
{
    m_szInternalResource = new char[nSize];
    std::fill(m_szInternalResource, &m_szInternalResource[nSize], nDefaultValue);
    m_szInternalResource[nSize - 1] = 0x00;
    m_nBufSize = nSize;
}


EmbeddedClass::~EmbeddedClass(void)
{
    if (NULL != m_szInternalResource)
            delete[] m_szInternalResource;
}

EmbeddedClass::EmbeddedClass(const EmbeddedClass& rhs)
: m_nBufSize(rhs.m_nBufSize), m_szInternalResource(new char[rhs.m_nBufSize])
{
    std::copy(rhs.m_szInternalResource, &rhs.m_szInternalResource[rhs.m_nBufSize], m_szInternalResource);
}

void EmbeddedClass::Print()
{
    std::cout << m_szInternalResource << std::endl;
}



Указатель на этот класс имеется в другом классе и память выделяется на этапе создания:




// MainClass.h

#include "EmbeddedClass.h"

class MainClass
{
public:
    MainClass(void);
    MainClass(int nSize, int nDefaultVal = 0);
    MainClass(const MainClass& rhs);
    virtual ~MainClass(void);

    void Print();

protected:
    EmbeddedClass * embedded;
};

// MainClass.cpp

#include "StdAfx.h"
#include "MainClass.h"

#include <iostream>

MainClass::MainClass(void)
    : embedded(new EmbeddedClass(0, 0x30))
{    
}

MainClass::MainClass(int nSize, int nDefaultVal)
    : embedded(nSize, nDefaultVal)
{    
}


MainClass::~MainClass(void)
{
    delete embedded;
}

MainClass::MainClass(const MainClass& rhs):
    embedded(new EmbeddedClass(???))
{

}

void MainClass::Print()
{
    embedded->Print();
}



Собственно вопрос заключается в том как проинициализировать EmbeddedClass, который входит в MainClass, когда используется семантика кучи (т.е. объект создается в динамической памяти посредством оператора new)? Если не использовать семантику кучи и работать без указателей, то конструктор копирования вызывается и нормально работает по следующей схеме:




MainClass::MainClass(const MainClass& rhs) : embedded(rhs.embedded)
{
}



Код тестового клиента, использующего эти классы:



#include "stdafx.h"

#include "EmbeddedClass.h"
#include "MainClass.h"


int _tmain(int argc, _TCHAR* argv[])
{
    EmbeddedClass embeddedClassA(1000, 0x31), embeddedClassB(1000, 0x32);

    EmbeddedClass embeddedClassC(embeddedClassA);

    embeddedClassA.Print();
    embeddedClassB.Print();
    embeddedClassC.Print();


    MainClass mainClassA(10, 0x33);

    MainClass mainClassB(mainClassA);

    mainClassA.Print();
    mainClassB.Print();

    return 0;
}
Re: Еще раз про copy constructor
От: uzhas Ниоткуда  
Дата: 16.07.10 15:07
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Добрый день, друзья!

Привет!
Так не получается?

MainClass::MainClass(const MainClass& rhs):
    embedded(new EmbeddedClass(*rhs.embedded))
{
}
Re[2]: Еще раз про copy constructor
От: c_plus_plus_newbie  
Дата: 16.07.10 15:17
Оценка:
Здравствуйте, uzhas, Вы писали:

U>Здравствуйте, Аноним, Вы писали:


А>>Добрый день, друзья!

U>Привет!
U>Так не получается?

U>
U>MainClass::MainClass(const MainClass& rhs):
U>    embedded(new EmbeddedClass(*rhs.embedded))
U>{
U>}
U>



Спасибо большое, получается!
Re: Еще раз про copy constructor
От: Caracrist https://1pwd.org/
Дата: 16.07.10 15:46
Оценка:
Здравствуйте, Аноним, Вы писали:

А>MainClass::MainClass(int nSize, int nDefaultVal)
А>    : embedded(nSize, nDefaultVal)
А>{    
А>}
А>


можно есще вот так написать:
MainClass::MainClass(int nSize, int nDefaultVal)
    : embedded(1,2,3,4,5,6, nSize, nDefaultVal)

и получишь тот же результат

имелось ввиду наверное вот это:
MainClass::MainClass(int nSize, int nDefaultVal)
    : embedded(new EmbeddedClass(nSize, nDefaultVal))

~~~~~
~lol~~
~~~ Single Password Solution
Re[2]: Еще раз про copy constructor
От: c_plus_plus_newbie  
Дата: 16.07.10 15:57
Оценка:
Здравствуйте, Caracrist, Вы писали:

C>Здравствуйте, Аноним, Вы писали:


C>
А>>MainClass::MainClass(int nSize, int nDefaultVal)
А>>    : embedded(nSize, nDefaultVal)
А>>{    
А>>}
А>>


C>можно есще вот так написать:

C>
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>

Да, согласен, тут явная бага, осталась от замены семантики стека на семантику кучи, а проблема была с разыменованием указателя у меня, не доходило никак почему не вызывается конструктор копирования Теперь все наконце-то встало на свои места
Re: Еще раз про copy constructor
От: Centaur Россия  
Дата: 17.07.10 06:21
Оценка: +1
Здравствуйте, Аноним, Вы писали:

А>/ EmbeddedClass.h
А>class EmbeddedClass
А>{
А>public:
А>    EmbeddedClass(void);
А>    EmbeddedClass(int nSize, int nDefaultValue = 0);
А>    EmbeddedClass(const EmbeddedClass& rhs);
А>    virtual ~EmbeddedClass(void);

А>    void Print();

А>protected:
А>    char * m_szInternalResource;
А>    int m_nBufSize;
А>};


Ко всему ранее сказанному добавлю, что здесь (и в следующем классе) нарушен 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 — неопределённое поведение


А кроме того, данные должны быть приватными.
Re[2]: Еще раз про copy constructor
От: c_plus_plus_newbie  
Дата: 19.07.10 11:38
Оценка:
Большое спасибо всем за советы! Они мне очень помогли!

Но чем дальше в лес, тем больше дров, и у меня возникла еще пара вопросов:

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:


// DerivedFromMainClass.h
class DerivedFromMainClass : public MainClass
{
public:
    DerivedFromMainClass(void);
    DerivedFromMainClass(int nSize, int nDefaultValue = 0x30);
    DerivedFromMainClass(const DerivedFromMainClass& rhs);
    DerivedFromMainClass& operator =(const DerivedFromMainClass& rhs);
    virtual ~DerivedFromMainClass(void);


private:
    char * m_szDerivedInternalResource;
    int m_nBufSize;
};

// DerivedFromMainClass.cpp
DerivedFromMainClass::DerivedFromMainClass(void)
    : m_szDerivedInternalResource(NULL),
      m_nBufSize(0)
{
}

DerivedFromMainClass::DerivedFromMainClass(int nSize, int nDefaultValue)
    : m_szDerivedInternalResource(new char[nSize]),
      m_nBufSize(nSize)
{
    std::fill(m_szDerivedInternalResource, &m_szDerivedInternalResource[nSize], nDefaultValue);
}

DerivedFromMainClass::~DerivedFromMainClass(void)
{
    if (NULL != m_szDerivedInternalResource)
            delete[] m_szDerivedInternalResource;
}

DerivedFromMainClass::DerivedFromMainClass(const DerivedFromMainClass& rhs)
    : m_nBufSize(rhs.m_nBufSize), m_szDerivedInternalResource(new char[rhs.m_nBufSize])
{
    std::copy(rhs.m_szDerivedInternalResource, &rhs.m_szDerivedInternalResource[m_nBufSize], m_szDerivedInternalResource);
}

DerivedFromMainClass& DerivedFromMainClass::operator =(const DerivedFromMainClass& rhs)
{
    if (this != &rhs)
    {
        delete[] m_szDerivedInternalResource;
        m_szDerivedInternalResource = new char[rhs.m_nBufSize];
        std::copy(rhs.m_szDerivedInternalResource, &rhs.m_szDerivedInternalResource[rhs.m_nBufSize], m_szDerivedInternalResource);
    }

    return *this;
}


то при определении конструктора копирования и оператора присваивания я должен заботиться только о внутренних ресурсах наследника? Все ресурсы классов родителей будут автоматически скопированны родительскими методами и эти методы будут вызваны также автоматически?
Re[3]: Еще раз про copy constructor
От: Alexey F  
Дата: 19.07.10 12:40
Оценка:
Здравствуйте, 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.

[...]
___> std::copy(rhs.m_szInternalResource, &rhs.m_szInternalResource[rhs.m_nBufSize], m_szInternalResource);

Да, можно так взять адрес.


___>2. Если я добавляю класс наследник допустим для класса MainClass:

[...]
___>то при определении конструктора копирования и оператора присваивания я должен заботиться только о внутренних ресурсах наследника? Все ресурсы классов родителей будут автоматически скопированны родительскими методами и эти методы будут вызваны также автоматически?
Вызов явно написать придётся:
// Конструктор копирования:
DerivedFromMainClass ( DerivedFromMainClass const& rhs ) : MainClass ( rhs ) { ... }

// Присваивание:
DerivedFromMainClass& operator= ( DerivedFromMainClass const& rhs ) {
    MainClass::operator= ( rhs );
    // [...]
    return *this;
}

После явного вызова можно заботиться только о ресурсах наследника.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.