1

I read a few tutorials about copy constructors on various web sites, wikipedia and also browsed through the first 5 pages of the "copy constructor" search results here on stackoverflow but I still don't get it :(

I have two questions:
1. What exactly does a copy constructor do?
2. Is a copy constructor always necessary (within a struct which contains a dynamically allocated array)?

I'll try to explain what bothers me and what I am actually asking with an example:

Let's say that I don't want to use the C++ string but instead, create my own "smart" string. I'll only provide the code necessary for my question:

#include <iostream>
#include <cstdlib>

using namespace std;

struct MyString
{
    char* str;
    int n , i;

    MyString(int x, char c)
    {
        str=(char*)malloc(x+1);

        for(n=0; n<x; ++n) str[n] = c;

        str[n] = '\0';
    }

    void print()
    {
        cout << str << endl;
    }

    void append(MyString second)
    {
        str=(char*)realloc(str, (n + second.n + 1) * sizeof(char));

        for(i=0; i < second.n; ++i) str[n+i] = second.str[i];

        n += second.n;
        str[n] = '\0';
    }

    ~MyString()
    {
        cout << "destructor!" << endl;
        free(str);
    }

};

int main()
{
    MyString A(5, '$'), B(5, '#');
    A.print();
    B.print();

    A.append(B);
    A.print();
    B.print();

    A.append(B);
    A.print();
    B.print();

}

To keep it simple, the struct contains only one constructor which for a given whole number "n" and a given character "c" produces a string which contains the character "c" repeated "n" times. Example: MyString A(5, '$'); means that A is the string "$$$$$". The function print, prints the string on the screen and the function append, appends one string to another string. Example: A.append(B); means A = A + B or in this example A becomes "$$$$$" + "#####" = "$$$$$#####".

There are a few things to notice:
1. MyString contains a dynamically allocated array.
2. The function append has "MyString" as a parameter.
3. I didn't include a copy constructor.

The function append is declared as follows:

void append(MyString second)

Normally this would mean that the function append receives a copy of an object of the type MyString but since MyString contains a dynamically allocated object, the function will receive a copy of a pointer (if I am correct?) to the original object and treat it as a local copy which means that after doing the appending, a destructor will be called upon that pointer and destroy that object so looking at my original main function:

int main()
{
    MyString A(5, '$'), B(5, '#');
    A.print();
    B.print();

    A.append(B);
    // B doesn't exist anymore

    A.print();  // OK
    B.print();  // ???

    A.append(B); // ???
    A.print();   // ???
    B.print();   // ???   

}

To fix the problem, I could write a copy constructor but do I really need one? I could write the function append as follows:

void append(MyString const& second)

or just

void append(MyString& second)

and this works but I was told that every time I encounter an object which involves dynamic allocation + a function which has the object type as a parameter, I should write a copy constructor, just to be safe. But what could go wrong? What could an user do to mess things up if I don't add a copy constructor?

I could write a copy constructor as follows:

 MyString(MyString const& second)
    {
        n = second.n;
        str = (char*)malloc(n);
        for(i=0; i<n; ++i) str[i] = second.str[i];
    }

and then I can leave the function append in it's original form:

void append(MyString second)

but what exactly happens when the following line is executed?

A.append(B);

I was told that constructors don't have a return value. So, if B calls his copy constructor before the function append (within A) is executed, how exactly is B "telling" A where to look for the copy of B?

And now I see that this question is already too big :( so I'll stop it here for now. Any edits, suggestions, comments and answers are welcome! Thanks in advance!

AltairAC
  • 171
  • 1
  • 10
  • 2
    You might want to have a look at [this](http://stackoverflow.com/questions/4782757/rule-of-three-becomes-rule-of-five-with-c11). The first answer gives a "rule of five". – Lemming May 30 '14 at 22:38
  • 1. This is C++, do not use `malloc()`, `new` is the C++ equivalent. 2. This is C++, do not use `new`, use `std::vector` instead. – Baum mit Augen May 30 '14 at 22:40
  • @Malloc "This is C++, do not use malloc()" so how did you end up in here? =P – AliciaBytes May 30 '14 at 22:43
  • @RaphaelMiedl Switched languages shortly after I joined, didn't think of another name since then. :) – Baum mit Augen May 30 '14 at 22:45
  • 1
    http://www.learncpp.com/cpp-tutorial/912-shallow-vs-deep-copying/ – Ryan Haining May 30 '14 at 22:52
  • 3
    Just add the declaration for it, make it private. If you need it then you'll know. – Hans Passant May 30 '14 at 22:57
  • 2
    Your question seems to be, **If I take great care to make sure I never copy my object, do I need a copy-constructor?** Clearly the answer is "no", however it is an extremely bad way to write code. Your code will be fragile and easy to break, as well as not being encapsulated , not reusable, and liable to trip up anybody else who uses your code (or yourself using it at a later date). – M.M May 30 '14 at 23:06
  • Lemming and Ryan Haining, thank you for the links, they helped me a lot! @Malloc Since I study mathematics, the emphasis is not on programming, we are doing basic programming and we migrated from C to C++ and malloc/realloc were used just as an example. I wanted to provide another example with "new" but the question is already too long. We are "forced" to use new and delete (no STL :( ) to prove that we understand it and I want to understand it. As if I would use "new" instead of std::vector if I didn't have to. :) – AltairAC May 31 '14 at 00:02
  • @MattMcNabb , as I mentioned in the first comment, we just do basic programming so I didn't understand the relevance of the copy constructor. Sadly, we were always given assignments in the form: "You can assume that bad stuff won't happen." so the user of the program is nice and careful (no bad/wrong input, no errors, ...) and that's why I thought: "I have a program which does exactly what it should, where could this possibly go wrong?". I didn't realize that it can go wrong in so many ways ... – AltairAC May 31 '14 at 00:10
  • 1
    @Shirohige as Hans says, if you don't want to write a copy-constructor, then at least put in a *declaration* of one, in the `private` section (or mark it as `= delete;` in C++11) - then the compiler will complain if you inadvertently attempt a copy. Do the same for `operator=`. BTW in your code sample in this post if you pass to `append` by reference then no copy is required. – M.M May 31 '14 at 00:32
  • @MattMcNabb I think you got me wrong. I don't have anything against writing a copy constructor but till now I just didn't realize that it is an useful (and in more serious programs also important) thing to do. – AltairAC May 31 '14 at 00:38
  • A general explanation about copy/move constructors and assignment operator= is [here](http://en.cppreference.com/w/cpp/language/rule_of_three) – Andrew Lazarus May 03 '16 at 23:06

1 Answers1

4

A copy constructor contains the instructions necessary to make a MyString object from another MyString object.

You absolutely need a copy constructor in this case, otherwise the semantics of the type MyString will be non-standard, and easy to get wrong.

The compiler will automatically implement a copy-constructor for you. Unfortunately, it will be probably be wrong because the object has pointers. This automatic implementation will just copy value of your pointer into another MyString. You will then have two MyString objects that think they own the same memory. One of them will get destroyed (its destructor will run) and the other will be left with a pointer that's no longer valid. Consider the following:

MyString first(10, 'a');
{ //This is here to create a new scope for the second myString
    MyString second(first); //The copy constructor that the compiler made for you runs here

    //. . . some other stuff
} //Right here, second goes out of scope and it's destructor runs.  This calls delete on str
  //Now you're in trouble - first's str pointer now points to unallocated memory
first.print(); //Uh-oh - undefined behavior.

Or:

//Declaration:
void SomeFunctionThatPassesByValue(MyString anotherMyString);

. . .

MyString first(10, 'a');
SomeFunctionThatPassesByValue(first); //The copy-constructor can run here too

/* Inside SomeFunctionThatPassesByValue, there will be a copy of first named
anotherMyString which will have its destructor run and call delete and de-allocate
your memory out from under you */

first.print(); //Uh-oh again!

Now, you could say "Anybody who uses MyString: be extremely careful and never copy it by value or else things are going to get screwed up" - but that's not very realistic. You should either explicitly disable the copy constructor (This is Hans Passant's suggestion in the comments) or you should implement it correctly.

You'll also need to implement (or disable) the copy-assign operator correctly too or you'll have the same problem.

MyString second(10, 'b');
second = first; //The copy-assign operator that the compiler made for you is also wrong!

You have some options for how to 'correctly' implement the copy constructor and the copy-assign operator. The simplest thing to do is allocate more memory and copy the memory that's pointed to. If the memory pointed to is immutable (It's not in your example), then you can share the pointer and count references and only call delete when the last MyString using that pointer is destroyed but that's hard to get right.

Pete Baughman
  • 2,996
  • 2
  • 19
  • 33
  • Thank you for your excellent answer. The examples helped me a lot! Just one more thing, I didn't include a second example with "new" since the question is already long enough. Provided that in the original implementation, there are functions which change the variable "score", is the copy constructor in "Team" written correctly? http://pastebin.com/z1RzFKG5 – AltairAC May 31 '14 at 00:17
  • 1
    It appears correct. You might want to use an initialization list, but it probably doesn't matter. You may also want to prefix your local fields with 'this->' to make it clear which fields are being assigned to. Don't forget to write a copy-assign operator too. The copy-assign will need clean up the object being assigned to (delete array) before copying. – Pete Baughman May 31 '14 at 05:16