0% found this document useful (0 votes)
30 views

Classes Compiler Synthesized Functions

The compiler will synthesize default versions of the copy constructor, assignment operator, destructor, and address-of operators for a class if they are not defined by the programmer. These default implementations perform shallow copies for non-static data members. The compiler will also synthesize a default constructor unless another constructor is defined. Programmers can explicitly request default implementations using = default or prevent them using = delete. The behavior of default implementations becomes undefined for classes with reference or const members.

Uploaded by

Casey Perdido
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
30 views

Classes Compiler Synthesized Functions

The compiler will synthesize default versions of the copy constructor, assignment operator, destructor, and address-of operators for a class if they are not defined by the programmer. These default implementations perform shallow copies for non-static data members. The compiler will also synthesize a default constructor unless another constructor is defined. Programmers can explicitly request default implementations using = default or prevent them using = delete. The behavior of default implementations becomes undefined for classes with reference or const members.

Uploaded by

Casey Perdido
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 6

Compiler-Synthesized Functions [Prasanna Ghali]

Functions Synthesized by Compiler


The material in this handout is collected from the following references:

Sections 7.1 and 13.1 of the text book C++ Primer.


Various sections of Effective C++.
Various sections of More Effective C++.

For more information, see this Microsoft page on explicitly defaulted and deleted functions.

Compiler synthesized member functions


If you don't declare them yourself, C++ compilers will synthesize their own versions of a copy
constructor, an assignment operator, a destructor, and a pair of address-of operators.
Furthermore, if you don't declare any constructors, they will declare a default constructor for you,
too. All these functions will be public. This means that if you had defined the following class:

1 class Empty {
2 // empty by design
3 };

it's the same as if you'd written this:

1 class Empty {
2 public:
3  Empty();                            // default constructor
4  Empty(Empty const& rhs);            // copy constructor
5  ~Empty();                           // destructor
6  Empty& operator=(Empty const& rhs); // assignment operator
7  Empty* operator&();                 // address-of operators
8  Empty const* operator&() const;
9 };

These functions are generated only if they are needed, but it doesn't take much to need them.
The following code will cause each function to be generated:

1 Empty const e1;         // default ctor and dtor


2 Empty e2(e1);           // copy ctor
3 Empty e3{e1};           // copy ctor in modern C++
4 e3 = e1;                // assignment operator
5 Empty *pe2 = &e2;       // address-of operator (non-const)
6 Empty const *pe1 = &e1; // address-of operator (const)

Given that compilers are synthesizing functions for you, what do these functions do? Well, the
default constructor and the destructor don't really do anything. They just enable you to create
and destroy objects of the class. The default address-of operators just return the object's address.
These functions are effectively defined like this:

1/6
Compiler-Synthesized Functions [Prasanna Ghali]

1 inline Empty::Empty() {}
2 inline Empty::~Empty() {}
3 inline Empty* Empty::operator&() { return this; }
4 inline Empty const* Empty::operator&() const { return this; }

As for the copy constructor and the assignment operator, the official rule is this: the default copy
constructor (assignment operator) performs member-wise copy construction (assignment) of non-
static data members of the class. That is, if m is a non- static data member of type T in a class
C and C declares no copy constructor (assignment operator), m will be copy constructed
(assigned) using the copy constructor (assignment operator) defined for T , if there is one. If there
isn't, this rule will be recursively applied to m 's data members until a copy constructor
(assignment operator) or built-in type (e.g., int , double , pointer, etc.) is found. By default,
objects of built-in types are copy constructed (assigned) using bitwise copy from the source object
to the destination object.

default ed functions
Here's an example to understand how synthesized functions behave. Consider the definition of a
NamedInt class, whose instances are classes allowing you to associate names with int values:

1 class NamedInt {
2 public:
3  NamedInt(char const *name, int value);
4  NamedInt(std::string const& name, int value);
5  // we tell human readers that by design a copy ctor and op=
6  // must be generated:
7  NamedInt(NamedInt const&) = default;
8  NamedInt& operator=(NamedInt const&) = default;
9  // other stuff here that is not relevant to our discussion
10 private:
11  std::string nameValue;
12  int intValue;
13 };

You can explicitly ask the compiler to generate the synthesized versions of the copy-
control members by declaring them as = default .

Because the NamedInt classes declare at least one constructor, compilers won't generate default
constructors, but because the programmer has explicitly asked for the synthesized versions, the
compiler will generate those functions [if they're needed]. Note that if classes fail to declare copy
constructors or assignment operators, compilers will generate those functions [if they are
needed]. Consider the following call to a copy constructor:

1 NamedInt no1("Smallest Prime Number", 2);


2 NamedInt no2(no1);      // calls copy constructor

The copy constructor generated by the compiler must initialize no2.nameValue and
no2.intValue using no1.nameValue and no1.intValue , respectively. The type of nameValue is
string , and string has a copy constructor [which you can verify by examining string in the
standard library], so no2.nameValue will be initialized by calling the string copy constructor
with no1.nameValue as its argument. On the other hand, the type of NamedInt::intValue is

2/6
Compiler-Synthesized Functions [Prasanna Ghali]

int , and no copy constructor is defined for int s, so no2.intValue will be initialized by copying
the bits over from no1.intValue .

The code for class NamedInt and to test the keyword default can be found here.

delete d functions
The compiler-generated assignment operator for NamedInt would behave the same way, but in
general, compiler-generated assignment operators behave as described only when the resulting
code is both legal and has a reasonable chance of making sense. If either of these tests fails,
compilers will refuse to generate an operator= for your class.

For example, suppose a class NamedRefIntConst were defined like this, where nameValue is a
reference to a string and intValue is a int const :

1 class NamedRefIntConst {
2 public:
3  // this ctor no longer takes a const name, because nameValue is
4  // now a reference-to-non-const string. The char const* ctor is
5  // gone, because we must have a string to refer to
6  NamedRefIntConst(std::string& name, int value);
7  // assume no copy ctor nor operator= is declared ...
8  // other stuff here that is not relevant to our discussion
9 private:
10  std::string& nameValue;   // this is now a reference
11  int const intValue;       // this is now const
12 };

Now consider what should happen here:

1 std::string newCar("Beemer");
2 std::string oldCar("Porsche");
3 NamedRefIntConst p(newCar, 2); // I've owned this car for 2 years
4 NamedRefIntConst s(oldCar, 5); // I've owned this car for 5 years
5 // what should happen to the data members in p?
6 p = s;                              

Before the assignment, p.nameValue refers to some string object and s.nameValue also refers
to a string , though not the same one. How should the assignment affect p.nameValue ? After
the assignment, should p.nameValue refer to the string referred to by s.nameValue , i.e.,
should the reference itself be modified? However, that is impossible, because C++ doesn't provide
a way to make a reference refer to a different object. Alternatively, should the string object to
which p.nameValue refers be modified, thus affecting other objects that hold pointers or
references to that string , i.e., objects not directly involved in the assignment? Is that what the
compiler-generated assignment operator should do?

Faced with such a conundrum, C++ refuses to compile the code. If you want to support
assignment in a class containing a reference member, you must define the assignment operator
yourself. Compilers behave similarly for classes containing const members [such as intValue in
class NamedRefIntConst ]; it's not legal to modify const members, so compilers are unsure how
to treat them during an implicitly generated assignment function.

In the case of class NamedRefIntConst , the programmer can explicitly disallow use of compiler-
generated copy constructor and assignment operator using keyword delete :

3/6
Compiler-Synthesized Functions [Prasanna Ghali]

1 class NamedRefIntConst {
2 public:
3  // this ctor no longer takes a const name, because nameValue is
4  // now a reference-to-non-const string. The char const* ctor is
5  // gone, because we must have a string to refer to
6  NamedRefIntConst(std::string& name, int value);
7  
8  // explicitly disallow any type of copy operation!!!
9  NamedRefIntConst(NamedRefIntConst const&) = delete;
10  NamedRefIntConst& operator=(NamedRefIntConst const&) = delete;
11  // other stuff here that is not relevant to our discussion
12 private:
13  std::string& nameValue;   // this is now a reference
14  int const intValue;       // this is now const
15 };

The code to test the delete keyword can be found here.

Code for NamedInt


1 #include <iostream>
2 #include <string>
3
4 class NamedInt {
5 public:
6  NamedInt(char const *name, int value)
7   : nameValue(name), intValue(value) {}
8  NamedInt(std::string const& name, int value)
9   : nameValue(name), intValue(value) {}
10  
11  NamedInt& operator=(NamedInt const&) = default;
12  NamedInt(NamedInt const&) = default;
13
14  void setName(std::string const& name) { nameValue = name; }
15  std::string const& getName() const { return nameValue; }
16  void setInt(int value) { intValue = value; }
17  int const& getInt() const { return intValue; }
18
19 private:
20  std::string nameValue;
21  int intValue;
22 };
23
24 std::ostream& operator<<(std::ostream& os, NamedInt const& rhs) {
25  os << "(" << rhs.getName() << ", " << rhs.getInt() << ")";
26  return os;
27 }
28
29 int main() {
30  NamedInt no1("Smallest Prime Number", 2);
31  NamedInt no2(no1);      // calls copy constructor
32  std::cout << "no1: " << no1 << "\n";
33  std::cout << "no2: " << no2 << "\n";
34
35  no2.setName("Next Prime Number Is");
36  no2.setInt(3);

4/6
Compiler-Synthesized Functions [Prasanna Ghali]

37  no1 = no2; // calls copy-assignment operator


38  std::cout << "no1: " << no1 << "\n";
39  std::cout << "no2: " << no2 << "\n";
40 }

Code for NamedRefIntConst


1 #include <iostream>
2 #include <string>
3
4 class NamedRefIntConst {
5 public:
6  // this ctor no longer takes a const name, because nameValue is
7  // now a reference-to-non-const string. The char const* ctor is
8  // gone, because we must have a string to refer to
9  NamedRefIntConst(std::string& name, int value)
10   : nameValue{name}, intValue{value} {
11      // empty by design
12   }
13  
14  // explicitly disallow any type of copy operation!!!
15  NamedRefIntConst(NamedRefIntConst const&) = delete;
16  NamedRefIntConst& operator=(NamedRefIntConst const&) = delete;
17
18  std::string const& getName() const { return nameValue; }
19  int                getInt()  const { return intValue; }
20  // other stuff here that is not relevant to our discussion
21 private:
22  std::string& nameValue;   // this is now a reference
23  const int intValue;       // this is now const
24 };
25
26 std::ostream& operator<<(std::ostream& os, NamedRefIntConst const& rhs) {
27  os << "(" << rhs.getName() << ", " << rhs.getInt() << ")";
28  return os;
29 }
30
31 int main() {
32  std::string newCar("Beemer");
33  std::string oldCar("Porsche");
34  NamedRefIntConst p(newCar, 2); // I've owned this car for 2 years
35  NamedRefIntConst s(oldCar, 5); // I've owned this car for 5
36  
37  std::cout << "p: " << p << "\n";
38  std::cout << "s: " << s << "\n";
39
40 // uncomment to get errors
41  //NamedRefIntConst t(s);
42  //std::cout << "t: " << t << "\n";
43  //p = s;
44 }

5/6
Compiler-Synthesized Functions [Prasanna Ghali]

6/6

You might also like