1

Just starting on C++ (a few days now), I come with C background.

I have a class that holds, mainly, an a pointer to an int array with the following code:

class Array
{
private:
    int * _arr;
    int _size;
public:
    Array();
    Array(int size);
    Array(const Array& obj);  // copy constructor
    ~Array();
    void readInValues();
    void mult(int num);
    void add(int num);
    void printArray();
};

_arr is a pointer to an int array, when creating a new instance using the copy constructor I would to create a new int array on the heap (I think). In the copy constructor:

Array::Array( const Array & obj )
{
    _arr = new int[_size];

    for(int i=0;i<_size;i++)
        *_arr[i] = *obj._arr[i];
}

The 1st thing I do is allocate memory for the new array (_size is a primitive type so being copied automatically as far as I understood). The next thing I would like to do is copy the array itself using a loop. This parts fails the compilation saying illegal indirection. I am not sure why...

user34920
  • 227
  • 3
  • 6
  • 12
  • No, `_size` is not being copied. You have to do that yourself. Then, your loop should have `_arr[i] = obj._arr[i];`, but you can also use `std::copy`. You will also need to do something about the assignment operator. – juanchopanza Jun 15 '14 at 08:47
  • 6
    The **very first** thing you need to do when you come to C++ with C background is to **forget entirely about arrays and pointers**. C++ inherits these things from C for compatibility. Never use them unless you need to interface with C or old C++ code. Do not continue with other topics until you have eradicated these notions from your vocabulary. Use `std::vector`, `std::shared_ptr` and `std::unique_ptr` instead. – n. m. could be an AI Jun 15 '14 at 08:54
  • 2
    @n.m. I think this is more an exercise, as I'd doubt that anyone would still implement an array in c++ as it is provided by the standard library. Seeing it from that point of view, a good understanding of pointers and memory management is actually really important and will help you. also, raw pointers can be more performant on small scale and are not meaningless, as they can indicate different things in ownership: http://stackoverflow.com/questions/7657718/when-to-use-shared-ptr-and-when-to-use-raw-pointers – Pinna_be Jun 15 '14 at 09:10
  • Note that you also need an assignment operator and possibly a move constructor/move assignment operator. See Rule of Three/Five. – pmr Jun 15 '14 at 10:05

5 Answers5

7

This:

Array::Array( const Array & obj )
{
    _arr = new int[_size];

    for(int i=0;i<_size;i++)
        *_arr[i] = *obj._arr[i];
}

could be this:

Array::Array( const Array & obj )
    : _arr(obj._size ? new int[obj._size] : nullptr)
    , _size(obj._size)
{
    if (_arr)
        std::copy(obj._arr, obj._arr+_size, _arr);
}

Specifically, note the use of the initializer list. Other things worth mentioning:

  1. Don't use identifiers with leading underscores. Besides making the code a royal pain to read, you can quickly find yourself using identifiers that are reserved by the implementation. That isn't the case here, but I'm willing to bet that wasn't by intent.

  2. You also need an assignment operator to fulfill the Rule of Three. Thankfully, a decent copy constructor makes that trivial.

  3. Don't do any of this. This is why, with great wailing and much gnashing of teeth in design decisions, the almighty C++ standards committees of years-gone-by have so-chosen to bless us with std::vector<>. If you're doing this to learn, good. What you'll eventually learn is how rarely you need to once you embrace the standard library and all its guilty pleasures.

If this is intended as a learning exercise, kudos, and blissfully ignore (3) above. For (2), you can use the copy/swap idiom now that you have a reasonable copy-ctor:

Array& Array::operator =(Array obj) // value-param intentional
{
    std::swap(_arr, obj._arr);
    std::swap(_size, obj._size);
    return *this;
}

Best of luck.

Community
  • 1
  • 1
WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • why would you use swap in your assignment operator. I'd understand using move. don't you just create an unnecessary overhead by first calling copy constructor and then doing each member assignment three times with swap? Once to a temporary variable, once to a your "*this" and once to obj, which is destructed after return? – Pinna_be Jun 15 '14 at 09:40
  • The swap is relying on the temporary object doing the copy instead of doing the copy inside the operator =. It also relies on temporary obj freeing replaced _arr when destroyed. The down side to this approach apart from being less obvious, and can result in additional unwanted copying of member variables, as a swap is essentially copying two variables, as is the case for _size. The traditional method with const Array& would not need to do the extra copy. –  Jun 15 '14 at 10:00
  • @Pinna_be your question would be well-answered by reading the question and answered provided in the answer ([**see here**](http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom/3279550#3279550)). In short, swapping two *scalar* member variables is *nothing* compared to that dynamic allocation. The assignment operator as-written is exception-safe. Traditional `const` reference poses the problem of potentially having invalid-state when dynamic management comes to play. Read the link. It does a better job of describing in-detail than i could hope to in a comment. – WhozCraig Jun 15 '14 at 17:55
  • @WhozCraig thank you for the link. This can indeed often be a solution to write better code when copy constructor and assignmentinstructor have more or less same functionality. – Pinna_be Jun 15 '14 at 18:35
  • @Pinna_be Glad it helped. The real benefit is exception management. Its a hard thing to wrap your head around, but worth it to read about. – WhozCraig Jun 15 '14 at 18:37
0

_size has no initial values, since you have not passed it either as arguements or initialized anywhere else before using it.

Also, don't do *_arr[i] = *obj._arr[i];

It doesn't make sense as arr[i] itself means *(arr+i).

Do:

_arr[i] = obj._arr[i];
  • A copy of primitive data types is carried out only if there's a initial value inside the class declaration? The other constructors I have put a value in _size so when the copy constructor is called there's always a value in _size. Another thing obj._arr[i] is not returning the memory address of the i member of the array? – user34920 Jun 15 '14 at 08:53
  • No, `_size` is not copied. Also you just want to copy the values, so `_arr[i] = obj._arr[i];` would be fine. –  Jun 15 '14 at 09:14
0

when you do this, you should be fine

Array::Array( const Array & obj ):
    _size(obj._size) 
{
    _arr = new int[_size];

    for(int i=0;i<_size;i++)
        _arr[i] = obj._arr[i];
}

on line 2 you get the intializer list, read this http://www.cprogramming.com/tutorial/initialization-lists-c++.html

The point is, when you write a custom copy constructor, only the things you explicitly say you copy will be copied. The copy constructor will first call the default initilizer for all members except when you use the initializer value.

Also, a tiny hint for performance maybe, did you look into memcpy? http://www.cplusplus.com/reference/cstring/memcpy/ it might be more efficient :D

another thing I noticed is that you dereference *_arr[i], which is not a pointer but a reference to the value.

EDIT someone noticed that my even better was actually not better. I changed it.

Pinna_be
  • 4,517
  • 2
  • 18
  • 34
  • memcpy(_arr,obj._arr,sizeof(obj._arr)); ? seems to copy the memory addresses from obj._arr but memcpy waits for pointers and these are pointers. – user34920 Jun 15 '14 at 09:13
  • @user34920 I'd rather say memcpy(_arr, obj._arr, sizeof(_arr[0])*_size); the size of the pointer is as long as a long integer (on most machines), so you should take the sizeof the first element time the amount of elements you have. – Pinna_be Jun 15 '14 at 09:23
0

_size being a primitive type is being copied is actually a true statement, but you didn't specify the complete sentence.
When you don't declare a copy constructor the default implementation will be something as this

Array::Array( const Array & obj )
{
    this._size = obj._size;
    this._arr = obj._arr;
}

Which is what you don't want as the copied object will share the _arr variable.
But as you have implemented the copy constructor you need to implement it fully. In your case you not not used the value of _size. Hence it would be undefined.

The compilation failure is due to the line *_arr[i] = *obj._arr[i];. You are using an illegal indirection using the *. Just use _arr[i] = obj._arr[i]; to copy it.

Pratham
  • 1,522
  • 1
  • 18
  • 33
0

It's my solution. Maybe will help.

Array.h

#pragma once
class Array {

public:

    Array();
    Array(int size);
    Array(const Array& src);
    Array operator=(const Array& rhs);
    ~Array();

    void readInValues();
    void mult(int num);
    void add(int num);
    void printArray();

private:
    int *_arr;
    int _size;

};

I added the assignment operator in reference to the three rule. And it's Array.cpp

#include "Array.h"

Array::Array()
    :_size(10) {

    _arr = new int[_size];
}

Array::Array(int size)
    : _size(size) {

    _arr = new int[_size];
}

Array::Array(const Array& src) {
    _size = src._size;
    _arr = new int[_size];

    for (int i = 0; i < _size; i++) {
        _arr[i] = src._arr[i];
    }
}

Array Array::operator=(const Array& rhs) {
    if (this == &rhs) {
        return (*this);
    }

    delete[] _arr;

    _size = rhs._size;
    _arr = new int[_size];

    for (int i = 0; i < _size; i++) {
        _arr[i] = rhs._arr[i];
    }
}

Array::~Array() {
    delete[] _arr;
}