Why does the = operator work on structs without having been defined?

C++GccOperators

C++ Problem Overview


Let's look at a simple example:

struct some_struct {
   std::string str;
   int a, b, c;
}

some_struct abc, abc_copy;
abc.str = "some text";
abc.a = 1;
abc.b = 2;
abc.c = 3;

abc_copy = abc;

Then abc_copy is an exact copy of abc.. how is it possible without defining the = operator?

(This took me by surprise when working on some code..)

C++ Solutions


Solution 1 - C++

If you do not define these four methods (six in C++11) the compiler will generate them for you:

  • Default Constructor
  • Copy Constructor
  • Assignment Operator
  • Destructor
  • Move Constructor (C++11)
  • Move Assignment (C++11)

If you want to know why?
It is to maintain backward compatibility with C (because C structs are copyable using = and in declaration). But it also makes writing simple classes easier. Some would argue that it adds problems because of the "shallow copy problem". My argument against that is that you should not have a class with owned RAW pointers in it. By using the appropriate smart pointers that problem goes away.

Default Constructor (If no other constructors are defined)

> The compiler generated default constructor will call the base classes default constructor and then each members default constructor (in the order they are declared)

Destructor (If no destructor defined)

> Calls the destructor of each member in reverse order of declaration. Then calls the destructor of the base class.

Copy Constructor (If no copy constructor is defined)

> Calls the base class copy constructor passing the src object. Then calls the copy constructor of each member using the src objects members as the value to be copied.

Assignment Operator

> Calls the base class assignment operator passing the src object. Then calls the assignment operator on each member using the src object as the value to be copied.

Move Constructor (If no move constructor is defined)

> Calls the base class move constructor passing the src object. Then calls the move constructor of each member using the src objects members as the value to be moved.

Move Assignment Operator

> Calls the base class move assignment operator passing the src object. Then calls the move assignment operator on each member using the src object as the value to be copied.

If you define a class like this:

struct some_struct: public some_base
{   
    std::string str1;
    int a;
    float b;
    char* c;
    std::string str2;
};

What the compiler will build is:

struct some_struct: public some_base
{   
    std::string str1;
    int a;
    float b;
    char* c;
    std::string str2;

    // Conceptually two different versions of the default constructor are built
    // One is for value-initialization the other for zero-initialization
    // The one used depends on how the object is declared.
    //        some_struct* a = new some_struct;     // value-initialized
    //        some_struct* b = new some_struct();   // zero-initialized
    //        some_struct  c;                       // value-initialized
    //        some_struct  d = some_struct();       // zero-initialized
    // Note: Just because there are conceptually two constructors does not mean
    //       there are actually two built.
    
    // value-initialize version
    some_struct()
        : some_base()            // value-initialize base (if compiler generated)
        , str1()                 // has a normal constructor so just call it
        // PODS not initialized
        , str2()
   {}

    // zero-initialize version
    some_struct()
        : some_base()            // zero-initialize base (if compiler generated)
        , str1()                 // has a normal constructor so just call it.
        , a(0)
        , b(0)
        , c(0)   // 0 is NULL
        , str2()
        // Initialize all padding to zero
   {}

    some_struct(some_struct const& copy)
        : some_base(copy)
        , str1(copy.str1)
        , a(copy.a)
        , b(copy.b)
        , c(copy.c)
        , str2(copy.str2)
    {}
 
    some_struct& operator=(some_struct const& copy)
    {
        some_base::operator=(copy);
        str1 = copy.str1;
        a    = copy.a;
        b    = copy.b;
        c    = copy.c;
        str2 = copy.str2;
        return *this;
    }

    ~some_struct()
    {}
    // Note the below is pseudo code
    // Also note member destruction happens after user code.
    // In the compiler generated version the user code is empty
        : ~str2()
        // PODs don't have destructor
        , ~str1()
        , ~some_base();
    // End of destructor here.

    // In C++11 we also have Move constructor and move assignment.
    some_struct(some_struct&& copy)
                    //    ^^^^  Notice the double &&
        : some_base(std::move(copy))
        , str1(std::move(copy.str1))
        , a(std::move(copy.a))
        , b(std::move(copy.b))
        , c(std::move(copy.c))
        , str2(std::move(copy.str2))
    {}
 
    some_struct& operator=(some_struct&& copy)
                               //    ^^^^  Notice the double &&
    {
        some_base::operator=(std::move(copy));
        str1 = std::move(copy.str1);
        a    = std::move(copy.a);
        b    = std::move(copy.b);
        c    = std::move(copy.c);
        str2 = std::move(copy.str2);
        return *this;
    } 
};

Solution 2 - C++

In C++, structs are equivalent to classes where members default to public rather than private access.

C++ compilers will also generate the following special members of a class automatically if they are not provided:

  • Default constructor - no arguments, default initalizes everything.
  • Copy constructor - ie a method with the same name as the class, that takes a reference to another object of the same class. Copies all values across.
  • Destructor - Called when the object is destroyed. By default does nothing.
  • Assignment operator - Called when one struct/class is assigned to another. This is the automatically generated method that's being called in the above case.

Solution 3 - C++

That behavior is necessary in order to maintain source compatibility with C.

C does not give you the ability to define/override operators, so structs are normally copied with the = operator.

Solution 4 - C++

But it is defined. In the standard. If you supply no operator =, one is supplied to you. And the default operator just copies each of the member variables. And how does it know which way to copy each member? it calls their operator = (which, if not defined, is supplied by default...).

Solution 5 - C++

The assignment operator (operator=) is one of the implicitly generated functions for a struct or class in C++.

Here is a reference describing the 4 implicitly generated members:
http://www.cs.ucf.edu/~leavens/larchc++manual/lcpp_136.html

In short, the implicitly generated member performs a memberwise shallow copy. Here is the long version from the linked page:

> The implicitly-generated assignment operator specification, when needed, is the following. The specification says that the result is the object being assigned (self), and that the value of the abstract value of self in the post-state self" is the same as the value of the abstract value of the argument from.

// @(#)$Id: default_assignment_op.lh,v 1.3 1998/08/27 22:42:13 leavens Exp $
#include "default_interfaces.lh"

T& T::operator = (const T& from) throw();
//@ behavior {
//@   requires assigned(from, any) /\ assigned(from\any, any);
//@   modifies self;
//@   ensures result = self /\ self" = from\any\any;
//@   ensures redundantly assigned(self, post) /\ assigned(self', post);
//           thus
//@   ensures redundantly assigned(result, post) /\ assigned(result', post);
//@ }

Solution 6 - C++

The compiler will synthesise some members for you if you don't define them explicitly yourself. The assignment operator is one of them. A copy constructor is another, and you get a destructor too. You also get a default constructor if you don't provide any constructors of your own. Beyond that I'm not sure what else but I believe there may be others (the link in the answer given by 280Z28 suggests otherwise though and I can't remember where I read it now so maybe it's only four).

Solution 7 - C++

structs are basically a concatenation of its components in memory (with some possible padding built in for alignment). When you assign one struct the value of another, the values are just coped over.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionconejoroyView Question on Stackoverflow
Solution 1 - C++Martin YorkView Answer on Stackoverflow
Solution 2 - C++MHarrisView Answer on Stackoverflow
Solution 3 - C++FerruccioView Answer on Stackoverflow
Solution 4 - C++eranView Answer on Stackoverflow
Solution 5 - C++Sam HarwellView Answer on Stackoverflow
Solution 6 - C++TroubadourView Answer on Stackoverflow
Solution 7 - C++Scott M.View Answer on Stackoverflow