Форум программистов, компьютерный форум CyberForum.ru

Оператор копирования для объектов - C++

Восстановить пароль Регистрация
 
higimo
0 / 0 / 0
Регистрация: 13.02.2012
Сообщений: 10
16.04.2014, 12:05     Оператор копирования для объектов #1
Отсутствие, даже потенциальных, утечек памяти подразумевается.

Пишу класс, который реализует шаблонный динамический массив.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
template <class T>
class CMyArray
{
public:
  // Конструктор
  CMyArray():
    m_size(0), m_array(nullptr)
  {}
 
  // Конструктор с единственным элементом
  CMyArray(T const & arg)
  {
    m_size = 1;
    m_array = new T[1];
    m_array[0] = arg;
  }
 
  // Копирующий конструктор (что, правильный?)
  CMyArray(CMyArray const & value)
  {
    delete [] m_array;
    m_size = value.m_size;
    m_array = new T[m_size + 1];
    for (size_t i = 0; i < m_size; i++)
    {
      m_array[i] = new T(value.m_array[i]);
    }
  }
 
  // Деструктор
  ~CMyArray()
  {
    delete [] m_array;
  }
 
  // Оператор присваивания (валится при присваивании с объктами вроде стринга)
  CMyArray & operator=(CMyArray const & other)
  {
    delete [] m_array;
    m_array = new T[other.m_size + 1];
    memcpy(m_array, other.m_array, other.m_size + 1);
    m_size = other.m_size;
    return *this;
  }
 
private:
  T *m_array;
  size_t m_size;
}
Всё это работает… Но валится на тестах.

Класс, реализующий счетчик

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template <class T>
class CCountable
{
public:
    static size_t GetInstanceCount()
    {
        return m_counter;
    }
protected:
    CCountable()
    {
        ++m_counter;
    }
    CCountable(CCountable const& other)
    {
        ++m_counter;
    }
    ~CCountable()
    {
        --m_counter;
    }
private:
    static size_t m_counter;
};
template<class T> size_t CCountable<T>::m_counter = 0;
Структура выбрасывающая исключения

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct MyStruct : CCountable<MyStruct>
{
    MyStruct():shouldThrowOnAssignmentOrCopy(false)
    {}
    
    MyStruct(MyStruct const& other):shouldThrowOnAssignmentOrCopy(other.shouldThrowOnAssignmentOrCopy)
    {
        if (shouldThrowOnAssignmentOrCopy)
        {
            throw std::runtime_error("OOPS");
        }
    }
    MyStruct & operator=(MyStruct const& other)
    {
        if (other.shouldThrowOnAssignmentOrCopy)
        {
            throw std::runtime_error("OOPS");
        }
 
        shouldThrowOnAssignmentOrCopy = other.shouldThrowOnAssignmentOrCopy;
        return *this;
    }
    bool shouldThrowOnAssignmentOrCopy;
};
Сами тесты

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void TestThatWillFail()
{
    // Демонстрация, что тестирующая фигня работает
    assert(MyStruct::GetInstanceCount() == 0);
    {
        MyStruct s;
        assert(MyStruct::GetInstanceCount() == 1);
    }
    assert(MyStruct::GetInstanceCount() == 0);
 
    // Тестируем на вызовы конструкторов и деструкторов
    {
        CMyArray<MyStruct> arr0;
        arr0.Push((MyStruct()));
        try
        {
            CMyArray<MyStruct> arr;
            arr.Push((MyStruct()));
            assert(MyStruct::GetInstanceCount() == 2);
            arr[0].shouldThrowOnAssignmentOrCopy = true;
            arr0 = arr;
        }
        catch (...)
        {
            assert(arr0.Size() == 1);
            assert(MyStruct::GetInstanceCount() == 1);
        }
    }
    
    // Тестируем оператор присваивания
    {
        CMyArray<std::string> strings;
        strings.Push("Hello, this is Tester, I hope this test will fail");
        strings.Push("Hello, this is Tester, I hope this test will fail");
        CMyArray<std::string> strings1;
        strings1 = strings;
        //оператор копирования для объектов
    }
    {
        assert(MyStruct::GetInstanceCount() == 1);
        {
            try
            {
                MyStruct item;
                item.shouldThrowOnAssignmentOrCopy = true; 
                CMyArray<MyStruct> array(item);
            }
            catch(...){}
        }
        assert(MyStruct::GetInstanceCount() == 1);
 
    }
 
    assert(MyStruct::GetInstanceCount() == 0);
}
Я раньше оператор копирования писал как конструктор копирования (идентичный код был), но он валился на тестах, где проверялся вызов деструкторов. Когда копирую куски памяти появляется проблема с тем, что string не копируется на самом деле.

Помогите, как решить проблему? Может ссылка какая есть? Всякие свапы, которые кругом предлагают не покатят — класс шаблонный.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
16.04.2014, 12:05     Оператор копирования для объектов
Посмотрите здесь:

C++ Написать программу, содержащую оператор оператор for для счета от 2 до 5 через 1
Конструктор копирования и оператор присваивания C++
C++ Про конструктор копирования, оператор присваивания
Очередь, конструктор копирования и перегруженный оператор присваивания C++
C++ Ребят, уже запарился, гляньте, что не так!? конструктор копирования и оператор присваивания
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
IrineK
Заблокирован
16.04.2014, 12:13     Оператор копирования для объектов #2
C++
1
T *m_array;
одномерный массив

Но в копировщике
C++
1
2
3
4
5
 m_array = new T[m_size + 1];
    for (size_t i = 0; i < m_size; i++)
    {
      m_array[i] = new T(value.m_array[i]);
    }
Предполагает массив двумерный, т.е. T **m_array; размером [m_size + 1] Х [m_size]
Или даже что-то зубастое.

В то же время в операторе присваивания
C++
1
m_array = new T[other.m_size + 1];
мы видим одномерный массив размером [m_size + 1]

C++
1
delete [] m_array;
удаляет одномерный массив

Определитесь, какой массив вы используете.
CheshireCat
Эксперт С++
2907 / 1235 / 78
Регистрация: 27.05.2008
Сообщений: 3,307
16.04.2014, 12:16     Оператор копирования для объектов #3
Цитата Сообщение от higimo Посмотреть сообщение
Всё это работает… Но валится на тестах.
Это как так????
alsav22
5282 / 4801 / 442
Регистрация: 04.06.2011
Сообщений: 13,587
16.04.2014, 12:22     Оператор копирования для объектов #4
Это, в конструкторе копирования, зачем?
C++
1
delete [] m_array;
Добавлено через 3 минуты
C++
1
2
3
4
5
6
7
8
9
CMyArray(CMyArray const & value)
{
    m_size = value.m_size;
    m_array = new T[m_size + 1];
    for (size_t i = 0; i < m_size; i++)
    {
          m_array[i] = value.m_array[i];
    }
}
higimo
0 / 0 / 0
Регистрация: 13.02.2012
Сообщений: 10
16.04.2014, 12:44  [ТС]     Оператор копирования для объектов #5
IrineK, ну я всё таки указал, что конструктор копирования может быть неверным. Так что точно я использую одномерный массив.
IrineK
Заблокирован
16.04.2014, 12:47     Оператор копирования для объектов #6
Цитата Сообщение от higimo Посмотреть сообщение
Так что точно я использую одномерный массив
Тогда используйте решение alsav22.
higimo
0 / 0 / 0
Регистрация: 13.02.2012
Сообщений: 10
16.04.2014, 12:52  [ТС]     Оператор копирования для объектов #7
IrineK, а оператор присваивания как реализовать? Автоматически код из оператора копирования не вытекает, а если подставить его в оператор присваивания тесты провалятся.
DrOffset
6424 / 3798 / 879
Регистрация: 30.01.2014
Сообщений: 6,591
17.04.2014, 18:35     Оператор копирования для объектов #8
Цитата Сообщение от higimo Посмотреть сообщение
а оператор присваивания как реализовать? Автоматически код из оператора копирования не вытекает, а если подставить его в оператор присваивания тесты провалятся.
Реализация копирования и присваивания не может быть идентичной в данном случае.
При копировании мы объект создаем, т.е. его до этого не существовало и мы задаем его начальное состояние идентичным аргументу. При присваивании объект уже существует - у него есть состояние. Можно сделать следующее и получить небольшой выигрыш в быстродействии:
1) Если размер выделенной памяти целевого объекта больше, либо равен размеру выделенной памяти присваиваемого, то можно не перераспределять память а просто скопировать в уже существующую присваиваемые элементы.
2) Если размер меньше, то делаем честное освобождение, затем распределение нужного количества. Потом копирование.
3) Для удобства, данная схема потребует ввести два размера. Один размер отражает общий размер выделенной памяти. Другой фактическое количество элементов в массиве.
IrineK
Заблокирован
17.04.2014, 18:52     Оператор копирования для объектов #9
Цитата Сообщение от higimo Посмотреть сообщение
string не копируется
Копируйте c_str() от string.
higimo
0 / 0 / 0
Регистрация: 13.02.2012
Сообщений: 10
19.04.2014, 08:36  [ТС]     Оператор копирования для объектов #10
DrOffset, «просто скопировать в уже существующую присваиваемые» можете показать как это правильно сделать в c++
DrOffset
6424 / 3798 / 879
Регистрация: 30.01.2014
Сообщений: 6,591
19.04.2014, 13:33     Оператор копирования для объектов #11
higimo, вот отрывок возможной реализации такого динамического массива (добавление элементов, индексация и т.п. отсутствует). В нем как раз реализована схема, которую я выше описал на словах. Полностью не привожу, чтобы была возможность самому дописать\поразмышлять. Надеюсь будет полезно.
Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include <algorithm>
#include <memory>
#include <cstdlib>
#include <new>
 
template <typename T>
class Array
{
public:
    typedef T *       pointer_type;
    typedef T         value_type;
 
    typedef T *       iterator;
    typedef T const * const_iterator;
 
private:
    struct Memory
    {
        pointer_type start;
        pointer_type finish;
        pointer_type end_of_storage;
  
        Memory() : start(), finish(), end_of_storage() {}
    };
 
public:
    ~Array()
    {
        destroy(this->mem_.start, this->mem_.finish);
        deallocate(this->mem_.start, this->mem_.end_of_storage - this->mem_.start);
    }
 
    Array & operator=(Array const & x)
    {
        if(&x != this)
        {
            const size_t xlen = x.size();
            if(xlen > capacity())
            {
                pointer_type tmp = allocate_and_copy(xlen, x.begin(), x.end());
 
                destroy(this->mem_.start, this->mem_.finish);
                deallocate(this->mem_.start, this->mem_.end_of_storage - this->mem_.start);
 
                this->mem_.start = tmp;
                this->mem_.end_of_storage = this->mem_.start + xlen;
            }
            else if(size() >= xlen)
            {
                destroy(std::copy(x.begin(), x.end(), begin()), end());
            }
            else
            {
                std::copy(x.mem_.start, x.mem_.start + size(), this->mem_.start);
                std::uninitialized_copy(x.mem_.start + size(), x.mem_.finish, this->mem_.finish);
            }
            this->mem_.finish = this->mem_.start + xlen;
        }
        return *this;
    }
 
    iterator begin()             { return mem_.start;  }
    const_iterator begin() const { return mem_.start;  }
    const_iterator end() const   { return mem_.finish; }
    iterator end()               { return mem_.finish; }
 
    size_t capacity() const
    {
        return size_t(this->mem_.end_of_storage - this->mem_.start);
    }
    size_t size() const
    {
        return size_t(this->mem_.finish - this->mem_.start);
    }
 
private:
    pointer_type allocate(size_t n, const void * = 0)
    {
        pointer_type ret = static_cast<T *>(std::malloc(n * sizeof(T)));
        if(!ret)
        {
            throw std::bad_alloc();
        }
        return ret;
    }
    void deallocate(pointer_type p, size_t)
    {
        std::free(static_cast<void*>(p));
    }
 
    pointer_type allocate_and_copy(size_t n, const_iterator first, const_iterator last)
    {
        pointer_type result = this->allocate(n);
        try
        {
            std::uninitialized_copy(first, last, result);
            return result;
        }
        catch(...)
        {
            deallocate(result, n);
            throw;
        }
    }
 
    void construct(pointer_type p, const T & val)
    {
        ::new(static_cast<void *>(p)) T(val);
    }
    void destroy(pointer_type p)
    {
        p->~T();
    }
    void destroy(const_iterator first, const_iterator last)
    {
        for(; first != last; ++first)
            destroy(first);
    }
 
    Memory mem_;
};
higimo
0 / 0 / 0
Регистрация: 13.02.2012
Сообщений: 10
21.04.2014, 11:48  [ТС]     Оператор копирования для объектов #12
DrOffset, простите, что так наглею, но не могли бы вы в качестве примера описать ещё метод .push(). Никак не получается построить аналогию смотря на ваш код и std::vector. Заранее спасибо.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
21.04.2014, 14:11     Оператор копирования для объектов
Еще ссылки по теме:

C++ оператор = и конструктор копирования
При удалении объектов происходит порча кучи. Конструктор копирования и оператор = переопределены C++
C++ Для чего нужен оператор двоеточие, если два двоеточия - это оператор расширения области видимости

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
DrOffset
6424 / 3798 / 879
Регистрация: 30.01.2014
Сообщений: 6,591
21.04.2014, 14:11     Оператор копирования для объектов #13
Цитата Сообщение от higimo Посмотреть сообщение
простите, что так наглею, но не могли бы вы в качестве примера описать ещё метод .push(). Никак не получается построить аналогию смотря на ваш код и std::vector. Заранее спасибо.
Можно как-то так:
Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#include <algorithm>
#include <memory>
#include <cstdlib>
#include <stdexcept>
#include <new>
 
template <typename T>
class Array
{
public:
    typedef T *       pointer_type;
    typedef T         value_type;
 
    typedef T *       iterator;
    typedef T const * const_iterator;
 
private:
    struct Memory
    {
        pointer_type start;
        pointer_type finish;
        pointer_type end_of_storage;
 
        Memory() : start(), finish(), end_of_storage() {}
    };
 
public:
    ~Array()
    {
        destroy(mem_.start, mem_.finish);
        deallocate(mem_.start, mem_.end_of_storage - mem_.start);
    }
 
    Array & operator=(Array const & x)
    {
        if(&x != this)
        {
            const size_t xlen = x.size();
            if(xlen > capacity())
            {
                pointer_type tmp = allocate_and_copy(xlen, x.begin(), x.end());
 
                destroy(mem_.start, mem_.finish);
                deallocate(mem_.start, mem_.end_of_storage - mem_.start);
 
                mem_.start = tmp;
                mem_.end_of_storage = mem_.start + xlen;
            }
            else if(size() >= xlen)
            {
                destroy(std::copy(x.begin(), x.end(), begin()), end());
            }
            else
            {
                std::copy(x.mem_.start, x.mem_.start + size(), mem_.start);
                std::uninitialized_copy(x.mem_.start + size(), x.mem_.finish, mem_.finish);
            }
            mem_.finish = mem_.start + xlen;
        }
        return *this;
    }
 
    iterator begin()
    {
        return mem_.start;
    }
    const_iterator begin() const
    {
        return mem_.start;
    }
    const_iterator end() const
    {
        return mem_.finish;
    }
    iterator end()
    {
        return mem_.finish;
    }
 
    size_t capacity() const
    {
        return size_t(mem_.end_of_storage - mem_.start);
    }
    size_t size() const
    {
        return size_t(mem_.finish - mem_.start);
    }
    size_t max_size() const
    {
        return size_t(-1u) / sizeof(T);
    }
 
    void push_back(value_type const & x)
    {
        if(mem_.finish != mem_.end_of_storage)
        {
            construct(mem_.finish, x);
            ++mem_.finish;
        }
        else
        {
            insert_impl(end(), x);
        }
    }
 
    iterator insert(iterator pos, value_type const & x)
    {
        const size_t n = std::distance(pos, begin());
        if(mem_.finish != mem_.end_of_storage && pos == end())
        {
            construct(mem_.finish, x);
            ++mem_.finish;
        }
        else
        {
            insert_impl(pos, x);
        }
        return mem_.start + n;
    }
 
    value_type & operator[](size_t n)
    {
        return *(mem_.start + n);
    }
    value_type const & operator[](size_t n) const
    {
        return *(mem_.start + n);
    }
 
private:
    pointer_type allocate(size_t n, const void * = 0)
    {
        if(n == 0)
        {
            return 0;
        }
        pointer_type ret = static_cast<T *>(std::malloc(n * sizeof(T)));
        if(!ret)
        {
            throw std::bad_alloc();
        }
        return ret;
    }
    void deallocate(pointer_type p, size_t)
    {
        std::free(static_cast<void*>(p));
    }
 
    pointer_type allocate_and_copy(size_t n, const_iterator first, const_iterator last)
    {
        pointer_type result = allocate(n);
        try
        {
            std::uninitialized_copy(first, last, result);
            return result;
        }
        catch(...)
        {
            deallocate(result, n);
            throw;
        }
    }
 
    void construct(pointer_type p, const T & val)
    {
        ::new(static_cast<void *>(p)) T(val);
    }
    void construct(pointer_type p)
    {
        ::new(static_cast<void *>(p)) T();
    }
    void destroy(pointer_type p)
    {
        p->~T();
    }
    void destroy(iterator first, iterator last)
    {
        for(; first != last; ++first)
        {
            destroy(first);
        }
    }
    size_t check_len(size_t n, char const * s) const
    {
        if(max_size() - size() < n)
            throw std::length_error(s);
 
        const size_t len = size() + std::max(size(), n);
        return (len < size() || len > max_size()) ? max_size() : len;
    }
 
    void insert_impl(iterator pos, T const & x)
    {
        if(mem_.finish != mem_.end_of_storage)
        {
            construct(mem_.finish);
            ++mem_.finish;
            std::copy_backward(pos, mem_.finish - 2, mem_.finish - 1);
            *pos = x;
        }
        else
        {
            const size_t len = check_len(1, "Array::insert");
            const size_t elems_before = pos - begin();
            pointer_type new_start  = allocate(len);
            pointer_type new_finish = new_start;
 
            try
            {
                construct(new_start + elems_before, x);
                new_finish = 0;
 
                new_finish = std::uninitialized_copy(mem_.start, pos, new_start);
                ++new_finish;
                new_finish = std::uninitialized_copy(pos, mem_.finish, new_finish);
            }
            catch(...)
            {
                if(!new_finish)
                {
                    destroy(new_start + elems_before);
                }
                else
                {
                    destroy(new_start, new_finish);
                }
                deallocate(new_start, len);
                throw;
            }
            destroy(mem_.start, mem_.finish);
            deallocate(mem_.start, mem_.end_of_storage - mem_.start);
 
            mem_.start  = new_start;
            mem_.finish = new_finish;
            mem_.end_of_storage = new_start + len;
        }
    }
 
    Memory mem_;
};

Реализация боевая, в том смысле, что здесь учтены и безпасность исключений и порядок операций требующихся при конструировании внутри массива.
Yandex
Объявления
21.04.2014, 14:11     Оператор копирования для объектов
Ответ Создать тему
Опции темы

Текущее время: 03:13. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru