Constructors in C++11

Professional C++ 2e Book Cover Ed’s Note: This is an extract from the forthcoming book Professional C++, 2nd Edition by Marc Gregoire, Nicholas A. Solter and Scott J. Kleper copyright John Wiley & Sons 2011. For Source Code, Sample Chapters, the Author Forum and other resources, go to the book's homepage on wrox.com

The object life cycle involves three activities: creation, destruction, and assignment. It is important to understand how and when objects are created, destroyed, and assigned, and how you can customize these behaviours.

Objects are created at the point you declare them (if they’re on the stack) or when you explicitly allocate space for them with new or new[]. When an object is created, all its embedded objects are also created. For example:

#include <string>
class MyClass 
{
  protected:
    std::string mName; 
}; 
int main() 
{     
  MyClass obj;     
  return 0; 
}

The embedded string object is created at the point where the MyClass object is created in the main() function and is destructed when its containing object is destructed.

It is often helpful to give variables initial values as you declare them. For example, you should usually initialize integer variables to 0 like this:

int x = 0;

Similarly, you should give initial values to objects. You can provide this functionality by declaring and writing a special method called a constructor, in which you can perform initialization work for the object. Whenever an object is created, one of its constructors is executed.

Writing Constructors

Syntactically, a constructor is specified by a method name that is the same as the class name. A constructor never has a return type and may or may not have parameters. A constructor with no parameters is called the default constructor. There are many contexts in which you may have to provide a default constructor and you will get compiler errors if you have not provided one. Default constructors will be discussed later in the chapter.

Here is a first attempt at adding a constructor to the SpreadsheetCell class:

class SpreadsheetCell 
{
  public:
    SpreadsheetCell(double initialValue);
  // Remainder of the class definition omitted for brevity 
};

Just as you must provide implementations for normal methods, you must provide an implementation for the constructor:

SpreadsheetCell::SpreadsheetCell(double initialValue) 
{
  setValue(initialValue); 
}

The SpreadsheetCell constructor is a method of the SpreadsheetCell class, so C++ requires the normal SpreadsheetCell:: scope resolution before the method name. The method name itself is also SpreadsheetCell, so the code ends up with the funny looking SpreadsheetCell::SpreadsheetCell. The implementation simply makes a call to setValue() in order to set both the numeric and text representations.

Using Constructors

Using the constructor creates an object and initializes its values. You can use constructors with both stack-based and heap-based allocation.

Constructors on the Stack

When you allocate a SpreadsheetCell object on the stack, you use the constructor like this:

SpreadsheetCell myCell(5), anotherCell(4);
cout << "cell 1: " << myCell.getValue() << endl; 
cout << "cell 2: " << anotherCell.getValue() << endl;

Note that you do NOT call the SpreadsheetCell constructor explicitly. For example, do not use something like the following:

SpreadsheetCell myCell.SpreadsheetCell(5); // WILL NOT COMPILE!

Similarly, you cannot call the constructor later. The following is also incorrect:

SpreadsheetCell myCell; 
myCell.SpreadsheetCell(5); // WILL NOT COMPILE!

Again, the only correct way to use the constructor on the stack is like this:

SpreadsheetCell myCell(5);

Constructors on the Heap

When you dynamically allocate a SpreadsheetCell object, you use the constructor like this:

SpreadsheetCell *myCellp = new SpreadsheetCell(5); 
SpreadsheetCell *anotherCellp = nullptr; 
anotherCellp = new SpreadsheetCell(4); 
// ... do something with the cells 
delete myCellp; 
myCellp = nullptr; 
delete anotherCellp; 
anotherCellp = nullptr;

Note that you can declare a pointer to a SpreadsheetCell object without calling the constructor immediately, which is different from objects on the stack, where the constructor is called at the point of declaration.

Whenever you declare a pointer on the stack or in a class, it should be initialized to nullptr like in the previous declaration for anotherCellp. If you don’t assign it to nullptr, the pointer is undefined. Accidentally using an undefined pointer will cause unexpected and difficult-to-diagnose memory corruption. If you initialize it to nullptr, using that pointer will cause a memory access error in most operating environments, instead of producing unexpected results.

As usual, remember to call delete on the objects that you dynamically allocate with new or use smart pointers!

Providing Multiple Constructors

You can provide more than one constructor in a class. All constructors have the same name (the name of the class), but different constructors must take a different number of arguments or different argument types. In C++, if you have more than one function with the same name, the compiler will select the one whose parameter types match the types at the call site. This is called overloading and is discussed in detail in Chapter 7.

In the SpreadsheetCell class, it is helpful to have two constructors: one to take an initial double value and one to take an initial string value. Here is the class definition with the second constructor:

class SpreadsheetCell 
{
  public:
    SpreadsheetCell(double initialValue);
    SpreadsheetCell(string initialValue);
  // Remainder of the class definition omitted for brevity 
};

Here is the implementation of the second constructor:

SpreadsheetCell::SpreadsheetCell(string initialValue) 
{ 
  setString(initialValue); 
}

And here is some code that uses the two different constructors:

SpreadsheetCell aThirdCell("test"); // Uses string-arg ctor 
SpreadsheetCell aFourthCell(4.4); // Uses double-arg ctor 
SpreadsheetCell* aThirdCellp = new SpreadsheetCell("4.4"); // string-arg ctor 
cout << "aThirdCell: " << aThirdCell.getValue() << endl;
cout << "aFourthCell: " << aFourthCell.getValue() << endl; 
cout << "aThirdCellp: " << aThirdCellp->getValue() << endl; 
delete aThirdCellp; 
aThirdCellp = nullptr;

When you have multiple constructors, it is tempting to attempt to implement one constructor in terms of another. For example, you might want to call the double constructor from the string constructor as follows:

SpreadsheetCell::SpreadsheetCell(string initialValue) 
{ 
  SpreadsheetCell(stringToDouble(initialValue)); 
}

That seems to make sense. After all, you can call normal class methods from within other methods. The code will compile, link, and run, but will not do what you expect. The explicit call to the SpreadsheetCell constructor actually creates a new temporary unnamed object of type SpreadsheetCell. It does not call the constructor for the object that you are supposed to be initializing.

C++11 introduces a new feature called delegating constructors, which allows constructors to call another constructor from the same class. This will be discussed later in this chapter.

Default Constructors

A default constructor is a constructor that takes no arguments. It is also called a 0-argument constructor. With a default constructor, you can give initial values to data members even though the client did not specify them.

When You Need a Default Constructor

Consider arrays of objects. The act of creating an array of objects accomplishes two tasks: It allocates contiguous memory space for all the objects and it calls the default constructor on each object. C++ fails to provide any syntax to tell the array creation code directly to call a different constructor. For example, if you do not define a default constructor for the SpreadsheetCell class, the following code does not compile:

SpreadsheetCell cells[3]; // FAILS compilation without default constructor 
SpreadsheetCell* myCellp = new SpreadsheetCell[10]; // Also FAILS

You can circumvent this restriction for stack-based arrays by using initializers like these:

SpreadsheetCell cells[3] = 
  {SpreadsheetCell(0), SpreadsheetCell(23), SpreadsheetCell(41)};

However, it is usually easier to ensure that your class has a default constructor if you intend to create arrays of objects of that class.

A default constructor is also required for classes that you want to store in an STL container like std::vector. STL containers are explained in detail in Chapter 12.

Default constructors are also useful when you want to create objects of that class inside other classes, which is shown later in this chapter under the section Constructor Initializers.

Finally, default constructors are convenient when the class serves as a base class of an inheritance hierarchy. In that case, it’s convenient for subclasses to initialize superclasses via their default constructors. Chapter 8 covers this issue in more detail.

How To Write a Default Constructor

Here is part of the SpreadsheetCell class definition with a default constructor:

class SpreadsheetCell 
{ 
  public:
    SpreadsheetCell();
    // Remainder of the class definition omitted for brevity 
};

Here is a first crack at an implementation of the default constructor:

SpreadsheetCell::SpreadsheetCell() 
{ 
  mValue = 0; 
  mString = ""; 
}

You use the default constructor on the stack like this:

SpreadsheetCell myCell;
myCell.setValue(6); 
cout << "cell 1: " << myCell.getValue() << endl;

The preceding code creates a new SpreadsheetCell called myCell, sets its value, and prints out its value. Unlike other constructors for stack-based objects, you do not call the default constructor with function-call syntax. Based on the syntax for other constructors, you might be tempted to call the default constructor like this:

SpreadsheetCell myCell(); // WRONG, but will compile.
myCell.setValue(6);       // However, this line will not compile. 
cout << "cell 1: " << myCell.getValue() << endl;

Unfortunately, the line attempting to call the default constructor will compile. The line following it will not compile. The problem is that your compiler thinks the first line is actually a function declaration for a function with the name myCell that takes zero arguments and returns a SpreadsheetCell object. When it gets to the second line, it thinks that you’re trying to use a function name as an object!

When creating an object on the stack, omit parentheses for the default constructor.

However, when you use the default constructor with a heap-based object allocation, you are required to use function-call syntax:

SpreadsheetCell* myCellp = new SpreadsheetCell(); // Note the function-call syntax

Don’t waste a lot of time pondering why C++ requires different syntax for heap-based versus stack-based object allocation with a default constructor. It’s just one of those things that makes C++ such an exciting language to learn.

Compiler-Generated Default Constructor

The first SpreadsheetCell class definition in this chapter looked as follows:

class SpreadsheetCell 
{ 
  public: 
    void setValue(double inValue); 
    double getValue() const; 
  protected: 
    double mValue; 
};

This definition does not declare a default constructor, but still, the code that follows works perfectly.

SpreadsheetCell myCell; 
myCell.setValue(6);

The following definition is the same as the preceding definition except that it adds an explicit constructor, accepting a double. It still does not explicitly declare a default constructor.

With this definition, the following code will not compile anymore:

SpreadsheetCell myCell; myCell.setValue(6);

What’s going on here? The reason is that if you don’t specify any constructors, the compiler will write one for you that doesn’t take any arguments. This compiler-generated default constructor calls the default constructor on all object members of the class, but does not initialize the language primitives such as int and double. Nonetheless, it allows you to create objects of that class. However, if you declare a default constructor, or any other constructor, the compiler no longer generates a default constructor for you.

A default constructor is the same thing as a 0-argument constructor. The term default constructor does not refer only to the constructor automatically generated if you fail to declare any constructors. It refers to the constructor which is defaulted to if there are no arguments.

C++11 Explicitly Defaulted Constructors

In older versions of C++, if your class required a number of explicit constructors accepting arguments but also a default constructor that does nothing, you had to explicitly write your empty default constructor as follows:

class MyClass 
{ 
  public: 
    MyClass() {} 
    MyClass(int i); 
};

However, it’s recommended that interface files contain only declarations of public methods without any implementations. The preceding class definition violates this. The solution was to define the class as follows:

class MyClass 
{ 
  public: 
    MyClass(); 
    MyClass(int i); 
};

The implementation of the empty default constructor in the implementation file would be:

MyClass::MyClass() { }

To avoid having to write empty default constructors manually, C++11 introduces the concept of explicitly defaulted constructors. This allows you to write the class definition as follows without the need to implement it in the implementation file.

class MyClass 
{ 
  public:
    MyClass() = default;
    MyClass(int i); 
};

MyClass defines a custom constructor that accepts one integer. However, the compiler will still generate a standard compiler generated default constructor due to the use of the default keyword.

C++11 Explicitly Deleted Constructors

C++11 also supports the concept of explicitly deleted constructors. For example, you can define a class for which you do not want to write any constructors and you also do not want the compiler to generate the default constructor. In that case you need to explicitly delete the default constructor:

class MyClass 
{ 
  public: 
    MyClass() = delete; 
};

Constructor Initializers

Up to now, this chapter initialized data members in the body of a constructor, for example:

SpreadsheetCell::SpreadsheetCell() 
{ 
  mValue = 0; 
  mString = ""; 
}

C++ provides an alternative method for initializing data members in the constructor, called the constructor initializer or ctor-initializer. Here is the 0-argument SpreadsheetCell constructor rewritten to use the ctor-initializer syntax:

SpreadsheetCell::SpreadsheetCell() : mValue(0), mString("") 
{
}

As you can see, the ctor-initializer appears syntactically between the constructor argument list and the opening brace for the body of the constructor. The list starts with a colon and is separated by commas. Each element in the list is an initialization of a data member using function notation or a call to a superclass constructor (see Chapter 8).

Initializing data members with a ctor-initializer provides different behavior than does initializing data members inside the constructor body itself. When C++ creates an object, it must create all the data members of the object before calling the constructor. As part of creating these data members, it must call a constructor on any of them that are themselves objects. By the time you assign a value to an object inside your constructor body, you are not actually constructing that object. You are only modifying its value. A ctor-initializer allows you to provide initial values for data members as they are created, which is more efficient than assigning values to them later. Interestingly, the default initialization for string s gives them the empty string; so explicitly initializing mString to the empty string as shown in the preceding example is superfluous.

If your class has as data member an object without a default constructor, you have to use the ctor-initializer to properly construct that object. For example, take the following SpreadsheetCell class:

class SpreadsheetCell 
{ 
  public: 
    SpreadsheetCell(double d); 
};

This class only has one explicit constructor accepting a double and does not include a default constructor. You can use this class as a data member of another class as follows:

class SomeClass 
{ 
  public: 
    SomeClass(); 
  protected: 
    SpreadsheetCell mCell; 
};

And implement the SomeClass constructor as follows:

SomeClass::SomeClass() { }

However, with this implementation, the following line will not compile. The compiler does not know how to initialize the mCell data member of SomeClass because it does not have a default constructor.

SomeClass s;

The solution is to initialize the mCell data member in the ctor-initializer as follows:

SomeClass::SomeClass() : mCell(1.0) { }

Ctor-initializers allow initialization of data members at the time of their creation.

Some programmers prefer to assign initial values in the body of the constructor. However, several data types must be initialized in a ctor-initializer. The following table summarizes them:

DATA TYPE

EXPLANATION

const data members

You cannot legally assign a value to a const variable after it is created. Any value must be supplied at the time of creation.

Reference data members

References cannot exist without referring to something.

Object data members for which there is no default constructor

C++ attempts to initialize member objects using a default constructor. If no default constructor exists, it cannot initialize the object.

Superclasses without default constructors

[Covered in Chapter 8]

There is one important caveat with ctor-initializers: They initialize data members in the order that they appear in the class definition, not their order in the ctor-initializer. Take the following definition for the SpreadsheetCell class:

class SpreadsheetCell 
{ 
  public: 
    // Code omitted for brevity 
  protected: 
    // Code omitted for brevity
    double mValue;
    string mString;
};

Suppose you write your SpreadsheetCell string constructor to use a ctor-initializer like this:

SpreadsheetCell::SpreadsheetCell(string initialValue) :
  mString(initialValue), mValue(stringToDouble(mString)) // INCORRECT ORDER!
{ 
}

The code will compile (although some compilers issue a warning), but the program does not work correctly. You might assume that mString will be initialized before mValue because mString is listed first in the ctor-initializer. But C++ doesn’t work that way. The SpreadsheetCell class declares mValue before mString; thus, the ctor-initializer tries to initialize mValue before mString. However, the code to initialize mValue tries to use the value of mString, which is not yet initialized! The solution in this case is to use the initialValue argument instead of mString when initializing mValue. You should also swap their order in the ctor-initializer to avoid confusion:

SpreadsheetCell::SpreadsheetCell(string initialValue) :
  mValue(stringToDouble(initialValue)), mString(initialValue)
{ 
}

Ctor-Initializers initialize data members in their declared order in the class definition, not their order in the ctor-initializer list.

Copy Constructors

There is a special constructor in C++ called a copy constructor that allows you to create an object that is an exact copy of another object. If you don’t write a copy constructor yourself, C++ generates one for you that initializes each data member in the new object from its equivalent data member in the source object. For object data members, this initialization means that their copy constructors are called.

Here is the declaration for a copy constructor in the SpreadsheetCell class:

class SpreadsheetCell 
{ 
  public:
    SpreadsheetCell(const SpreadsheetCell& src);
    // Remainder of the class definition omitted for brevity 
};
The copy constructor takes a const reference to the source object. Like other constructors, it does not return a value. Inside the constructor, you should copy all the data fields from the source object. Technically, of course, you can do whatever you want in the copy constructor, but it’s generally a good idea to follow expected behavior and initialize the new object to be a copy of the old one. Here is a sample implementation of the SpreadsheetCell copy constructor:

SpreadsheetCell::SpreadsheetCell(const SpreadsheetCell& src) : 
  mValue(src.mValue), mString(src.mString) 
{ 
}

Note the use of the ctor-initializer. The difference between setting values in the ctor-initializer and in the copy constructor body is examined later in the section on assignment.

Given a set of member variables, called m1, m2, ... mn, the compiler-generated copy constructor can be expressed as:

classname::classname(const classname& src) :
  m1(src.m1), m2(src.m2), ... mn(src.mn) { }

Therefore, in most circumstances, there is no need for you to specify a copy constructor yourself. However, under certain conditions, this default copy constructor is not sufficient. These conditions are covered in Chapter 7.

When the Copy Constructor Is Called

The default semantics for passing arguments to functions in C++ is pass-by-value. That means that the function or method receives a copy of the value or object. Thus, whenever you pass an object to a function or method the compiler calls the copy constructor of the new object to initialize it. For example, recall that the definition of the setString() method in the SpreadsheetCell class looks like this:

void SpreadsheetCell::setString(string inString) 
{ 
  mString = inString; 
  mValue = stringToDouble(mString); 
}

Recall, also, that the C++ string is actually a class, not a built-in type. When your code makes a call to setString() passing a string argument, the string parameter inString is initialized with a call to its copy constructor. The argument to the copy constructor is the string you passed to setString(). In the following example, the string copy constructor is executed for the inString object in setString() with name as its parameter.

SpreadsheetCell myCell; 
string name = "heading one"; 
myCell.setString(name); // Copies name

When the setString() method finishes, inString is destroyed. Because it was only a copy of name, name remains intact.

The copy constructor is also called whenever you return an object from a function or method. In this case, the compiler creates a temporary, unnamed, object through its copy constructor. Chapter 24 explores the impact of temporary objects in more detail. You can avoid the overhead of copy constructors by passing parameters as const references, which will be explained in a later section.

Calling the Copy Constructor Explicitly

You can use the copy constructor explicitly as well. It is often useful to be able to construct one object as an exact copy of another. For example, you might want to create a copy of a SpreadsheetCell object like this:

SpreadsheetCell myCell2(4);
SpreadsheetCell myCell3(myCell2); // myCell3 has the same values as myCell2

Passing Objects by Reference

In order to avoid copying objects when you pass them to functions and methods you can declare that the function or method takes a reference to the object. Passing objects by reference is usually more efficient than passing them by value, because only the address of the object is copied, not the entire contents of the object. Additionally, pass-by-reference avoids problems with dynamic memory allocation in objects, which is discussed in Chapter 7.

When you pass an object by reference, the function or method using the object reference could change the original object. When you’re only using pass-by-reference for efficiency, you should preclude this possibility by declaring the object const as well. Here is the SpreadsheetCell class definition in which string objects are passed as const references:

class SpreadsheetCell 
{ 
  public: 
    SpreadsheetCell(); 
    SpreadsheetCell(double initialValue);
    SpreadsheetCell(const string& initialValue);
    SpreadsheetCell(const SpreadsheetCell& src); 
    void setValue(double inValue); 
    double getValue() const;
    void setString(const string& inString);
    string getString() const; 
  protected: 
    string doubleToString(double inValue) const;
    double stringToDouble(const string& inString) const;
    double mValue; 
    string mString; 
};

Here is the implementation for setString(). Note that the method body remains the same; only the parameter type is different.

void SpreadsheetCell::setString(const string& inString)
{ 
  mString = inString; 
  mValue = stringToDouble(mString); 
}

For performance reasons, it is best to pass objects by const reference instead of by value.

The SpreadsheetCell methods that return a string still return it by value. Returning a reference to a data member is risky because the reference is valid only as long as the object is “alive.” Once the object is destroyed, the reference is invalid. However, there are sometimes legitimate reasons to return references to data members, as you will see later in this chapter and in subsequent chapters.

C++11 Explicitly Defaulted and Deleted Copy Constructor

You can explicitly default or delete a compiler generated copy constructor as follows:

SpreadsheetCell(const SpreadsheetCell& src) = default;

or

SpreadsheetCell(const SpreadsheetCell& src) = delete;

C++11 Initializer-List Constructors

An initializer-list constructor is a constructor with std::initializer_list<T> as first argument, without any additional arguments or with additional arguments having default values. Before you can use the std::initializer_list<T> template you need to include the <initializer_list> header. The following class demonstrates its use.

class PointSequence 
{ 
  public: 
    PointSequence(initializer_list<double> args) 
    { 
      if (args.size() % 2 != 0) 
      { 
        throw invalid_argument("initializer_list should " 
          "contain even number of elements."); 
      } 
      for (auto iter = args.begin(); iter != args.end(); ++iter) 
        mVecPoints.push_back(*iter); 
    } 
    void dumpPoints() const 
    { 
      for (auto citer = mVecPoints.cbegin(); 
        citer != mVecPoints.cend(); citer += 2) 
        { 
          cout << "(" << *citer << ", " << *(citer+1) << ")" << endl; 
        } 
    } 
  protected: 
    vector<double> mVecPoints; 
};

Inside the initializer-list constructor you can access the elements of the initializer-list by using iterators as shown in the previous example. Iterators are discussed in detail in Chapter 12. You can get the number of elements in the initializer-list with the size() method.

Objects of PointSequence can be constructed as follows:

PointSequence p1 = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; 
p1.dumpPoints(); 
try 
{ 
  PointSequence p2 = {1.0, 2.0, 3.0}; 
} 
catch (const invalid_argument& e) 
{  
  cout << e.what() << endl; 
}

The construction of p2 will throw an exception because it has an odd number of elements in the initializer-list. The preceding equal signs are optional and can be left out, for example:

PointSequence p1{1.0, 2.0, 3.0, 4.0, 5.0, 6.0};

The C++11 STL has full support for initializer-list constructors. For example, the std::vector container can be initialized by using an initializer-list:

std::vector<std::string> myVec = {"String 1", "String 2", "String 3"};

If you use a compiler that does not yet conform to the C++11 standard, one way to initialize the vector is by using several push_back() calls:

std::vector<std::string> myVec; 
myVec.push_back("String 1"); 
myVec.push_back("String 2"); 
myVec.push_back("String 3");

Initializer-list constructors are also important in the context of the C++11 uniform initialization feature as explained in the section “Uniform Initialization” in Chapter 9. Initializer lists are not limited to constructors and can also be used with normal functions as explained in Chapter 9.

C++11 In-Class Member Initializers

C++11 allows member variables to be initialized directly in the class definition. For example:

#include <string> 
class MyClass 
{ 
  protected: 
    int mInt = 1; 
    std::string mStr = "test"; 
};

The only way you could initialize mInt and mStr before C++11 was within a constructor body, or with a ctor-initializer as follows:

#include <string> 
class MyClass 
{ 
  public: 
    MyClass() : mInt(1), mStr("test") {} 
  protected: 
    int mInt; 
    std::string mStr; 
};

Before C++11, only static const integral member variables could be initialized in the class definition. For example:

#include <string> class MyClass { protected: static const int kI1 = 1; // OK static const std::string kStr = "test"; // Error: not integral type static int sI2 = 2; // Error: not const const int kI3 = 3; // Error: not static };

C++11 Delegating Constructors

Delegating constructors allow constructors to call another constructor from the same class. However, this call cannot be placed in the constructor body; it should be in the constructor initializer. Following is an example:

SpreadsheetCell::SpreadsheetCell(const string& initialValue)
  : SpreadsheetCell(stringToDouble(initialValue))
{
}
When this string constructor (the delegating constructor) is called, it will first delegate the call to the target, double constructor. When the target constructor returns, the body of the delegating constructor will be executed.

Make sure you avoid constructor recursion while using delegate constructors. For example:

class MyClass 
{ 
  MyClass(char c) : MyClass(1.2) { } 
  MyClass(double d) : MyClass('m') { } 
};

The first constructor will delegate to the second constructor, which delegates back to the first one. The behaviour of such code is undefined by the standard and depends on the compiler.

Summary of Compiler-Generated Constructors

The compiler will automatically generate a 0-argument constructor and a copy constructor for every class. However, the constructors you define yourself replace these constructors according to the following rules:

IF YOU DEFINE…

…THEN THE COMPILER GENERATES…

…AND YOU CAN CREATE AN OBJECT…

[no constructors]

A 0-argument constructor

A copy constructor

With no arguments:

SpreadsheetCell cell;

As a copy of another object:

SpreadsheetCell myCell(cell);

A 0-argument constructor only

A copy constructor

With no arguments:

SpreadsheetCell cell;

As a copy of another object:

SpreadsheetCell myCell(cell);

A copy constructor only

No constructors

Theoretically, as a copy of another object.

Practically, you can’t create any objects.

A single-argument (non-copy constructor) or multi-argument constructor only

A copy constructor

With arguments:

SpreadsheetCell cell(6);

As a copy of another object:

SpreadsheetCell myCell(cell);

A 0-argument constructor as well as a single-argument constructor (non-copy constructor) or multi-argument constructor

A copy constructor

With no arguments:

SpreadsheetCell cell;

With arguments:

SpreadsheetCell myCell(5);

As a copy of another object:

SpreadsheetCell anotherCell(cell);

Note the lack of symmetry between the default constructor and the copy constructor. As long as you don’t define a copy constructor explicitly, the compiler creates one for you. On the other hand, as soon as you define any constructor, the compiler stops generating a default constructor.

As mentioned before in this chapter, the automatic generation of a default constructor and a default copy constructor can be influenced in C++11 by defining them as explicitly defaulted or explicitly deleted.

You might also like...

Comments

About the author

Dan Maharry

Dan Maharry United Kingdom

Dan Maharry is the editor of DeveloperFusion. He has covered the latest in software development since the mid 90s. Not wishing to preach what he doesn't practice, Dan has also worked as a profes...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“In theory, theory and practice are the same. In practice, they're not.”