1

I'm somewhat new to C++ so, I guess this is a very basic question.

Suppose I have this class:

// 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;
}

I want to create an assignment operator that will copy the contents of array M[] but not to overwrite it when I change it after the copy (I think this is accomplished by not copying the actual pointer but only the content, don't know if I'm right). This is what I've tried:

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;
 }

Is this correct? Are there other ways to do this?

edit: An important question I forgot. Why I must declare it like Graph& operator=(Graph other); and not just: Graph operator=(Graph other); which is what's written in my book (C++: The Complete Reference, 2nd ed, Herbert Schildt, pages 355-357)?

Rafael S. Calsaverini
  • 13,582
  • 19
  • 75
  • 132
  • 2
    You might find [this FAQ](http://stackoverflow.com/questions/4172722/) interesting. If you replace `int *` with `std::vector`, you save yourself a world of pain. – fredoverflow Nov 24 '10 at 13:47

8 Answers8

9

The canonical way is to use a std::vector<int> to avoid managing memory yourself. For the exercise though, the right way to do what you want is:

#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 "copy and swap idiom" for the rationale behind the code. Note that your assigment operator leaks memory (the original array is overwritten but never deleted) and if the allocation fails, you end up with a broken object. Moreover, x = x won't do what it is expected to do. These three pitfalls are common, and writing assignment operators in copy-and-swap style avoids them.

EDIT: For your other question, returning a reference allows you to chain assignments, like a = b = c, which is valid for built in types. It may or may not be what you want (it usually is).

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • is `"copy and swap idiom"` (which I am not familiar with) a reason for passing the param to the assignment op by value and not by reference? – davka Nov 24 '10 at 13:53
  • 2
    @davka: yes. The assignment operator "copies and swap with *this" its argument. The copy is made when passing by value, and the standard allows the compiler to avoid this copy if not necessary. – Alexandre C. Nov 24 '10 at 13:55
  • +1 for suggesting the C++ way, `vector` - I'd actually emphasize that part more. – Mark B Nov 24 '10 at 14:26
3

You need the & in operator= so that it doesn't cause a copy when you return *this. The more important question is why do you need to return anything. The answer is to support a=b=c syntax.

I would suggest you use memcpy for standard arrays of built-in int-like types (or pointers). The standard defines it in a way that gives the compiler writer a way to provide the fastest possible, platform-specific implementation.

Do not use memcpy if the type is an object (won't call copy constructor and a lot of other bad things)

Lou Franco
  • 87,846
  • 14
  • 132
  • 192
  • 1
    doesn't `std::copy` use the most efficient method of copying objects (which may be `memcpy` for dumb objects)? I'd think that would be more idiomatic in C++ than mucking around with `memcpy`. – lijie Nov 24 '10 at 13:48
  • @lijie -- I'm not sure, but perhaps. – Lou Franco Nov 24 '10 at 14:51
1

You'd probably want to declare and define a copy constructor.

In fact, it is a must in your situation because the default copy constructor will result in a double delete during destruction.

I think it's more idiomatic to use the copy constructor in the assignment operator (a copy construction followed by a swap).

As for the current code, there is a memory leak (because the old M is not deleted).

lijie
  • 4,811
  • 22
  • 26
1

almost all have been said, but there are a few important notes:

  • it is advised to pass the parameter to copy constructor and assignment operator as const reference, i.e. const Graph& other, to avoid heavy copying to a temp object (unless you are using a "copy and swap" idiom
  • if you use pointer as a class member you must (well, should in most cases) provide both copy ctor and assignment operator, or disable them by making them private, otherwise the default ones will cause a leak.
  • finally, why not use std::vector? will save you all this trouble without noticeable performance penalty.

this page may be helpful - simple and comprehensive: C++ Operator Overloading Guidelines

davka
  • 13,974
  • 11
  • 61
  • 86
  • I think there is a case for passing by value to the assignment operator (the body of the assignment operator is then just a swap of *this and the parameter). – lijie Nov 24 '10 at 14:10
  • @lijie: yes, I've just learned it from Alexandre's answer. But one should use the complete idiom to make it a valid case – davka Nov 24 '10 at 14:30
  • hence, the first bullet point should be changed? it looks like a blanket "no" to passing the parameter by value to the assignment operator. – lijie Nov 24 '10 at 23:32
1

Some of it have already been said by others, but if we want to stick with basic layout that you have, you should change this:

  • Make "Graph other" constant -> "const Graph other" (Good to do, so you don´t 'accidentally' change the data in "other")
  • Have a check on "other" to see that it is not the same object as the object you are assigning to(the Lvalue). Can do this by making a simple if statement -> if(this == &other)
  • Delete the memory in M. (We don want memory leaks :) )

Oh btw. You don´t have to use C syntax by declaring "i" in the start of the function -> "for(int i =...."

Hope it helped :)

Salizer
  • 11
  • 2
0

You could also use std::copy more here std::copy

or you can memcpy arrays as well

Marek Szanyi
  • 2,348
  • 2
  • 22
  • 28
0

Don't forget that it's legal to write graph_a = graph_a; your code will leak the memory originally allocated for graph_a.M, and you'll end up with uninitialised memory in M after the copy.

Before doing a copy-assignment, you must check that you're not copying the same object over itself (in which case you can just return).

Chowlett
  • 45,935
  • 20
  • 116
  • 150
0

You must return a reference to the object that you have constructed. If you returned a copy, you would then proceed to copy construct another object, discarding the one that you already have.

Francesco
  • 3,200
  • 1
  • 34
  • 46