C++ compiler 'shallow' copies and assignments
Asked Answered
O

4

7

I'm taking a class on object oriented programming using C++.

In our text it says,

If we do not declare a copy constructor, the compiler inserts code that implements a shallow copy. If we do not declare an assignment operator, the compiler inserts code that implements a shallow assignment.

What I want to know, is whether this is in fact true, what the alluded to compiler mechanism is actually called, and how it works.

This is not a question about copy constructors, it is about compiler behavior.

EDIT> More context

Copy Constructor as defined by the text:

The definition of a copy constructor contains logic that

  1. performs a shallow copy on all of the non-resource instance variables
  2. allocates memory for each new resource
  3. copies data from the source resource(s) to the newly created resource(s)

Resource as defined by the text

Memory that an object allocates at run-time represents a resource of that object's class.

The management of this resource requires additional logic that was unnecessary for simpler classes that do not access resources. This additional logic ensures proper handling of the resource and is often called deep copying and assignment.

Osmic answered 22/10, 2015 at 16:58 Comment(3)
Without any context, the quote sounds at best confusing.Caracul
According to "en.wikipedia.org/wiki/Object_copying" shallow-copy is properly used (Agreed, it may confuse C++ programmers)Conjunctive
The statement is TRUE.Hexamethylenetetramine
N
8

It's more accurate to say that the compiler defines a default copy constructor and a default copy assignment operator. These will copy/construct the new object by simply calling the copy constructor for all of the member variables.

  • For primitives like ints and floats, this usually isn't a problem.
  • For pointers, though. This is bad news! What happens when the first object deletes that pointer? Now the other object's pointer is invalid!
  • If a member variable cannot be copied (perhaps you used a std::unique_ptr to fix the above problem), then the default copy assignment/ctor won't work. How can you copy something that can't be copied? This will lead to a compiler error.

If you define your own copy constructor/assignment operator, you can make a "deep copy" instead. You can:

  • Create a new object, rather than copying the pointer
  • Explicitly "shallow copy" a pointer
  • Mix the above two based on what you actually want!
  • Initialize member variables with default/custom values in copied objects rather than copy whatever was in the original object.
  • Disable copying altogether
  • On and on and on

As you can see, there are plenty of reasons why you'd want to implement (or explicitly prohibit) your own copy assignment operator, copy constructor, their move counterparts, and destructor. In fact, there's a well-known C++ idiom known as The Rule of Five (formerly the Rule of 3) that can guide your decision on when to do this.

Nahshunn answered 22/10, 2015 at 17:5 Comment(4)
Plenty of reasons? Which ones? If you find yourself implementing the special member functions, chances are that you made a design mistake somewhere.Caracul
then the default copy assignment/ctor will fail not the default will be explicitly marked as delete if the class is not copyable. coliru.stacked-crooked.com/a/40da9a87d79923e9Decommission
1. I'd paraphrase the part about pointers: it's not always "bad news". It's bad (will cause undefined behaviour and runtime errors) only if the object is responsible for freeing the resource (e.g. calls delete in it's destructor). 2. I'd also mention that for types that define custom copy operations (e.g std::vector) it invokes their custom behaviour. For example vector's constructor copies it's contents.Earn
While your answer is insightful, I'm still not sure what code the compiler is 'inserting'. I'm not sure what you mean by 'default constructor'Osmic
D
7

Yes it's true, and it's indeed called shallow copying. As for how it works, lets say you have a pointer variable, and you assign it to another pointer variable. This only copies the pointer and not what it points to, this is a shallow copy. A deep copy would have created a new pointer, and copied the actual contents that the first pointer points to.

Something like this:

int* a = new int[10];

// Shallow copying
int* b = a;   // Only copies the pointer a, not what it points to

// Deep copying
int* c = new int[10];
std::copy(a, a + 10, c);  // Copies the contents pointed to by a

The problem with shallow copying in regards to pointers should be quite obvious: After the initialization of b in the above example, you have two pointers both pointing to the same memory. If one then does delete[] a; then both pointers become invalid. If the two pointers are in different objects of some class, then there is no real connection between the pointers, and the second object won't know if the first object have deleted its memory.

Duwalt answered 22/10, 2015 at 17:5 Comment(5)
@hyde The std::string class (as well as other container classes like std::vector) have implemented copy-constructor and assignment operators to do deep copying.Duwalt
I understand what you are saying, and its a very clear answer... but I'm still not sure what code the compiler is 'inserting'.Osmic
@Osmic Read about the implicitly default-declared constructor , as well as about the implicitly declared copy-constructor and implicitly declared copy-assignment operator.Duwalt
@hyde Copying an array is still shallow. It copies all elements of the array as-is, which means that if the array is an array of pointers it work just like with other pointer, it copies the pointers and not what they point to.Duwalt
@JoachimPileborg After thinking about it, I have to concede the point. The whole concept of shallow versus deep copy only applies to reference types, so talking about string or array members only confuses the issue... Cleaning up comments above now.Varnado
H
2

The code for shallow copy is a simple assignment for every field. If:

class S {
  T f;
};
S s1, s2;

A assignment like s1=s2; is equivalent to what happens in the following:

class S {
    T f;
  public:
    S &operator=(const S&s) {
      this->f = s.f; // and such for every field, whatever T is
    }
};
S s1, s2;
s1=s2;

This is stated in 12.8-8 of the draft standard:

The implicitly-declared copy constructor for a class X will have the form X::X(const X&) if

— each direct or virtual base class B of X has a copy constructor whose first parameter is of type const B& or const volatile B&, and

— for all the non-static data members of X that are of a class type M (or array thereof), each such class type has a copy constructor whose first parameter is of type const M& or const volatile M&.123

Otherwise, the implicitly-declared copy constructor will have the form X::X(X&)

12.8-28 says:

The implicitly-defined copy/move assignment operator for a non-union class X performs memberwise copy/move assignment of its subobjects. [...] in the order in which they were declared in the class definition.

Hebrews answered 22/10, 2015 at 17:31 Comment(1)
So what code does the compiler insert in the case of a default constructor?Osmic
A
0

I'll use a basic class to define the behavior of the compiler as best as I know how to.

class Student sealed {
private:
    std::string m_strFirstName;
    std::string m_strLastName;

    std::vector<unsigned short> m_vClassNumbers;
    std::vector<std::string> m_vTeachers;

    std::vector<unsigned short> m_vClassGrades;

public:
    Student( const std::string& strFirstName, const std::string& strLastName );

    std::string getFirstName() const;
    std::string getLastName() const;

    void setClassRoster( std::vector<unsigned short>& vClassNumbers );
    std::vector<unsigned short>& getClassRoster() const;

    void setClassTeachers( std::vector<std::string>& vTeachers );
    std::vector<std::string>& getClassTeachers() const;

    void setClassGrades( std::vector<unsigned short>& vGrades );
    std::vector<unsigned short>& getGrades() const;

    // Notice That These Are Both Commented Out So The Compiler Will
    // Define These By Default. And These Will Make Shallow / Stack Copy
    // Student( const Student& c ); // Default Defined 
    // Student& operator=( const Student& c ); // Default Defined
};

The version of this class with its declaration by default will construct both a copy constructor and an equal operator.

class Student sealed {
private:
    std::string m_strFirstName;
    std::string m_strLastName;

    std::vector<unsigned short> m_vClassNumbers;
    std::vector<std::string> m_vTeachers;

    std::vector<unsigned short> m_vClassGrades;

public:
    Student( const std::string& strFirstName, const std::string& strLastName );

    std::string getFirstName() const;
    std::string getLastName() const;

    void setClassRoster( std::vector<unsigned short>& vClassNumbers );
    std::vector<unsigned short>& getClassRoster() const;

    void setClassTeachers( std::vector<std::string>& vTeachers );
    std::vector<std::string>& getClassTeachers() const;

    void setClassGrades( std::vector<unsigned short>& vGrades );
    std::vector<unsigned short>& getGrades() const;     

private:
    // These Are Not Commented Out But Are Defined In The Private Section
    // These Are Not Accessible So The Compiler Will No Define Them
    Student( const Student& c ); // Not Implemented
    Student& operator=( const Student& c ); // Not Implemented
};

Where the second version of this class will not since I declared both of them as being private!

This is probably the best way I can demonstrate this. I only showed the header file interface to this class since the c++ source or code to be compiled is not of a concern. The difference in how these two are defined during the Pre-Compile Stage dictates how the Compiler Will Work before it begins to compile the source code into object code.

Keep in mind though that the standard library strings & containers do implement their own Copy Constructor & Assignment Operators! But the same concept applies to the behavior of the compiler if a class has basic types such as int, float, double, etc. So the compiler will treat a Simple class in the same manner according to its declaration.

class Foo {
private:
    int   m_idx;
    float m_fValue;

public:
    explicit Foo( float fValue );

    // Foo( const Foo& c ); // Default Copy Constructor
    // Foo& operator=( const Foo& c ); // Default Assignment Operator
};

Second Version

class Foo {
private:
    int   m_idx;
    float m_fValue;

public:
    explicit Foo( float fValue );

private:
    Foo( const Foo& c ); // Not Implemented
    Foo& operator=( const Foo& c ); // Not Implemented
};

The compiler will treat this class in the same manner; it will not define either of these since they will not be implemented due to being declared as private.

Anselme answered 22/10, 2015 at 18:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.