2

So I have to write an operator= method in c++ that copies all the values of one array into another. Here's what I wrote:

dynamic_array &dynamic_array::operator=(const dynamic_array &a) {
    size = a.get_size();
    if (size % BLOCK_SIZE == 0){ //a multiple of BLOCK_SIZE
        allocated_size = size;
    } else {
        int full_blocks = size / BLOCK_SIZE;
        allocated_size  = (full_blocks+1) * BLOCK_SIZE;
    }
    try {
        array = new int[allocated_size];
    } catch (bad_alloc){
        throw exception (MEMORY_EXCEPTION);
    }

    //copy a[i..size-1]
    for (int i = 0; i < size; i++){
        array[i] = a[i];
    }
    return *this; //returns a reference to the object
}

So it doesn't assume anything about the sizes and sets the size and allocated size of the array it's given (and using another get_size() method). Now the second code I have to write just says I have to create an array containing a copy of the elements in a. Now I just wrote the same thing as I did for my operator= method (without returning anything):

dynamic_array::dynamic_array(dynamic_array &a) {
    size = a.get_size();
    if (size % BLOCK_SIZE == 0){ //a multiple of BLOCK_SIZE
        allocated_size = size;
    } else {
        int full_blocks = size / BLOCK_SIZE;
        allocated_size  = (full_blocks+1) * BLOCK_SIZE;
    }
    try {
        array = new int[allocated_size];
    } catch (bad_alloc){
        throw exception (MEMORY_EXCEPTION);
    }

    //copy a[i..size-1]
    for (int i = 0; i < size; i++){
        array[i] = a[i];
    }
}

Now this is giving me the output that I want but I'm just wondering if there's an easier way to do these two methods. The methods are for a dynamic array and I feel like there's more lines of code than needed. The operator= one is supposed to copy the elements of a into a new dynamic array and dynamic_array::dynamic_array(dynamic_array &a) { is supposed to create a new array containing a copy of the elements in a. It sounds like the same code for each method because you always need to create a new array and you will always need to copy the array elements from one array to another but is there a more simpler way to write these two methods or is this the simplest way to do it?

David Rolfe
  • 163
  • 2
  • 10
  • 9
    Look up the "copy and swap idiom" http://stackoverflow.com/q/3279543/214671 – Matteo Italia Feb 18 '16 at 06:47
  • 2
    What is the reason for catching `bad_alloc` only to throw `exception`? – juanchopanza Feb 18 '16 at 07:12
  • @MatteoItalia this is a case where copy-and-swap is quite suboptimal in performance terms – M.M Feb 18 '16 at 07:28
  • @M.M It is inefficient but the code in the question amounts to a hand-crafted copy and swap anyways! Only if it looked at the existing array and allocated size before deciding to allocate would I think it was gaining an edge. – Persixty Feb 18 '16 at 07:32
  • Yes it would be better if it did that – M.M Feb 18 '16 at 07:34

3 Answers3

2

You could simplify both the copy constructor and the copy assignment operator by changing the type of array to std::vector<int>.

The vector does all the work for you and you don't even need any custom implementations at all.

Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
0

First to make this simpler, you can make use of the fact that you have access to all private members of a. Also the use of memcpy will make this perform better. A couple of lines are added as well to delete the existing array before copying.

dynamic_array &dynamic_array::operator=(const dynamic_array &a) {
    size = a.size;
    allocated_size = a.allocated_size;

    int* newArray = new int[allocated_size];

    //copy array
    memcpy(newArray, a.array, allocated_size * sizeof(int));

    // Delete existing array
    if (array != NULL)
        delete[] array;

    array = newArray;

    return *this; //returns a reference to the object
}

Secondly, for copy constructor, a trick we used to do is to call the equal operator inside the copy constructor in order not to repeat the code:

dynamic_array::dynamic_array(dynamic_array &a) : array(NULL) {
    *this = a;
}
Bishoy
  • 705
  • 9
  • 24
  • It is also required to enclose the whole implementation of the assinment operator to `if (&a != this) {}` – dmi Feb 18 '16 at 07:03
  • Also where is the array `delete`d? Without the declaration of `dynamic array` it's hard to get right. – Persixty Feb 18 '16 at 07:09
  • And do be careful copying the allocated size of the source. Depending on how that is managed the target might over allocate. If the source was large and shrank and depending on it's shrinking strategy it could be vastly oversized. That might be acceptable but might propagate waste across the object graph. – Persixty Feb 18 '16 at 07:13
  • 1
    Added as well to the answer – Bishoy Feb 18 '16 at 07:13
  • You don't need to check for self-assignment if you first create the new array, then copy it, then delete the old one. – Frerich Raabe Feb 18 '16 at 07:15
  • but it would be a waste of CPU time - not that big deal anyway – Bishoy Feb 18 '16 at 07:17
  • 1
    I wouldn't worry about wasting a little CPU time for the rare case of self-assignment in exchange for getting rid of an `if`. – Frerich Raabe Feb 18 '16 at 07:18
  • I wouldn't worry about an `if` that adds clarity to a tricky issue like object aliasing. Logic that is cleverly alias-proof can be fragile and result in obscure bugs during an optimization drive. It can also deliver a significant performance increase even if self-assignments are relatively rare (e.g. ~0.1%) such is the high cost of allocation and copying. – Persixty Feb 18 '16 at 07:27
  • 1
    You could optimize this by not doing `delete` and `new` if the new allocated_size is equal or smaller – M.M Feb 18 '16 at 07:28
  • 1
    using `=` inside the copy-constructor is a bad idea. Itmeans you have to default-construct everything which could waste a lot of time. If you want to avoid duplication then it should be the assignment operator that calls the copy-constructor, not the other way around. (see "copy and swap") – M.M Feb 18 '16 at 07:30
  • 3
    suggest using `std::copy` rather than memcpy; it will behave better if the type is non-POD – M.M Feb 18 '16 at 07:32
  • That second 'trick' of implementing the copy constructor in terms of the copy assignment operator has the downside that you first initialise all members and then immediately overwrite them - instead of initialising them with the desired values right away. – Frerich Raabe Feb 18 '16 at 07:38
  • Only initialising the pointer in this case to prevent deletion of non existing data – Bishoy Feb 18 '16 at 07:43
  • You copy the allocated size whereas you only need to copy the *current* size. – Persixty Feb 18 '16 at 07:44
  • 1
    why not, just to make the copy simple (as the OP asked) I am bypassing the logic of recalculating the allocation size putting my trust in the other methods that once calculated it for the array I am copying from – Bishoy Feb 18 '16 at 07:45
0

From Scot Meyers (classic) book "Effective C++":

"In practice, the two copying functions will often have similar bodies, and this may tempt you to try to avoid code duplication by having one function call the other. Your desire to avoid code duplication is laudable, but having one copying function call the other is the wrong way to achieve it.

It makes no sense to have the copy assignment operator call the copy constructor, because you'd be trying to construct an object that already exists. This is so nonsensical, there's not even a syntax for it. There are syntaxes that look like you're doing it, but you're not; and there are syntaxes that do do it in a backwards kind of way, but they corrupt your object under some conditions. So I'm not going to show you any of those syntaxes. Simply accept that having the copy assignment operator call the copy constructor is something you don't want to do.

Trying things the other way around — having the copy constructor call the copy assignment operator — is equally nonsensical. A constructor initializes new objects, but an assignment operator applies only to objects that have already been initialized. Performing an assignment on an object under construction would mean doing something to a not-yet-initialized object that makes sense only for an initialized object. Nonsense! Don't try it.

Instead, if you find that your copy constructor and copy assignment operator have similar code bodies, eliminate the duplication by creating a third member function that both call. Such a function is typically private and is often named init. This strategy is a safe, proven way to eliminate code duplication in copy constructors and copy assignment operators."

And by the way, it'd be wise to also check for self-equality in you assignment operator.

Marinos K
  • 1,779
  • 16
  • 39
  • Effective C++ was originally published 14 years ago. These days, it's quite common to implement the copy assignment operator in terms of the copy constructor (namely, by passing the source object by value, i.e. `T &T::operator=(T)`). – Frerich Raabe Feb 18 '16 at 07:40
  • @FrerichRaabe could you redirect me to some example somewhere online or to some book? thx. – Marinos K Feb 18 '16 at 07:43
  • Well, I was imprecise: in `T &T::operator=(T)` you do work with a copy - but the copy constructor is not called by `operator=` but by the *caller* code. So technically, the assignment operator is not implemented in terms of copy construction, but the idea still applies for your argument (namely, reducing code duplication). – Frerich Raabe Feb 18 '16 at 07:45
  • Check the copy and swap idiom. It uses the code from copy constructor for operator=. http://stackoverflow.com/q/3279543/1771055 – rozina Feb 18 '16 at 08:32