Перегрузка = оператор в C ++, когда есть массивы значений для копирования - PullRequest
1 голос
/ 24 ноября 2010

Я немного новичок в C ++, так что, я думаю, это очень простой вопрос.

Предположим, у меня есть этот класс:

// file Graph.h
class Graph { 
public:
  Graph(int N); // contructor
  ~Graph();     // destructor  
  Graph& operator=(Graph other);
private:
  int * M;
  int N;
};

// file Graph.cpp
Graph :: Graph(int size) {
  M = new int [size];
  N = size;
}

Graph :: ~Graph() {
  delete [] M;
}

Я хочу создать оператор присваивания, который будет копировать содержимое массива M [], но , чтобы не перезаписывать его , когда я изменяю его после копирования (я думаю, это выполнено не копируя фактический указатель, а только содержимое, не знаю, прав ли я). Вот что я пробовал:

Graph& Graph::operator=(Graph other) {
  int i;
  N = other.N;
  M = new int [N];
  for (i = 0; i < N; i++)
    M[i] = other.M[i];
  return *this;
 }

Это правильно? Есть ли другие способы сделать это?

редактировать: важный вопрос, который я забыл. Почему я должен объявить это как Graph& operator=(Graph other);, а не просто: Graph operator=(Graph other);, что написано в моей книге (C ++: Полная ссылка, 2-е изд, Герберт Шильдт, стр. 355-357)?

Ответы [ 8 ]

9 голосов
/ 24 ноября 2010

Каноническим способом является использование std::vector<int>, чтобы избежать управления памятью самостоятельно.Однако для этого упражнения правильный способ сделать то, что вы хотите, это:

#include <algorithm>

class Graph
{
public:    
    Graph(size_t n) { data_ = new int[n](); size_ = n; }

    Graph(Graph const& g)
    {
        data_ = new int(g.size_);
        size_ = g.size_;
        std::copy(g.data_, g.data_ + g.size_, data_);
    }

    ~Graph() { delete[] data_; }

    void swap(Graph& g) throw()
    {
        std::swap(data_, g.data_);
        std::swap(size_, g.size_);
    }

    Graph& operator=(Graph g) { g.swap(*this); return *this; }

private:
    int* data_;
    size_t size_;
};

Google "скопируйте и меняйте идиому" для обоснования кода.Обратите внимание, что ваш оператор присваивает утечку памяти (исходный массив перезаписывается, но никогда не удаляется), и если распределение завершается неудачно, вы в итоге получаете сломанный объект.Более того, x = x не будет делать то, что ожидается.Эти три ловушки являются общими, и операторы присваивания при написании в стиле копирования и замены избегают их.

РЕДАКТИРОВАТЬ: Для вашего другого вопроса, возвращая ссылку, вы можете связать назначения, как a = b = c, который является действительнымдля встроенных типов.Это может или не может быть то, что вы хотите (обычно это так).

3 голосов
/ 24 ноября 2010

Вам нужно & в operator=, чтобы оно не вызывало копию при возврате *this. Более важный вопрос - зачем вам что-то возвращать. Ответ заключается в поддержке синтаксиса a=b=c.

Я бы предложил вам использовать memcpy для стандартных массивов встроенных int-подобных типов (или указателей). Стандарт определяет его таким образом, чтобы разработчик компилятора мог обеспечить максимально быструю реализацию для конкретной платформы.

Не используйте memcpy, если тип является объектом (не будет вызывать конструктор копирования и много других плохих вещей)

1 голос
/ 24 ноября 2010

Некоторые из них уже были сказаны другими, но если мы хотим придерживаться того базового макета, который у вас есть, вы должны изменить это:

  • Сделать "Graph other" константой -> "constГрафик «другое» (хорошо, чтобы вы не «случайно» изменили данные в «другое»)
  • Проверьте «другое», чтобы убедиться, что это не тот же объект, что и объект, который выприсваивают (Lvalue).Это можно сделать, сделав простое выражение if -> if (this == & other)
  • Удалить память в M. (Мы не хотим утечек памяти :))

Кстати,Вам не нужно использовать синтаксис Си, объявляя «i» в начале функции -> «для (int i = ....»

Надеюсь, это помогло:)

1 голос
/ 24 ноября 2010

почти все сказано, но есть несколько важных замечаний:

  • рекомендуется передавать параметр для копирования конструктора и оператора присваивания в виде константной ссылки , то есть const Graph& other, чтобы избежать интенсивного копирования во временный объект (если вы не используете "копию и поменять "идиома
  • если вы используете указатель в качестве члена класса, вы должны (ну, в большинстве случаев, должны) предоставить и копию ctor, и оператор присваивания, или отключить их, сделав их закрытыми, в противном случае значения по умолчанию вызовут течь.
  • наконец, почему бы не использовать std::vector? избавит вас от всех этих хлопот без заметного снижения производительности.

эта страница может быть полезной - простой и понятной: Рекомендации по перегрузке операторов C ++

1 голос
/ 24 ноября 2010

Возможно, вы захотите объявить и определить конструктор копирования.

На самом деле, это необходимо в вашей ситуации, потому что конструктор копирования по умолчанию приведет к двойному delete во время уничтожения.

Я думаю, что более идиоматично использовать конструктор копирования в операторе присваивания (конструкция копирования сопровождается перестановкой).

Что касается текущего кода, то есть утечка памяти (потому что старый M не delete d).

0 голосов
/ 24 ноября 2010

Вы должны вернуть ссылку на построенный вами объект.Если вы вернули копию, вы затем приступили бы к копированию конструкции другого объекта, отбрасывая тот, который у вас уже есть.

0 голосов
/ 24 ноября 2010

Не забывайте, что писать законно graph_a = graph_a; ваш код утечет память, первоначально выделенную для graph_a.M, и вы получите неинициализированную память в M после копирования.

Перед выполнением назначения копирования вы должны убедиться, что вы не копируете один и тот же объект поверх себя (в этом случае вы можете просто вернуться).

0 голосов
/ 24 ноября 2010

Вы также можете использовать std :: copy больше здесь std :: copy

или вы также можете memcpy массивы

...