Appearance
Advanced Constructors
C++ provides several types of constructors beyond the basic constructor to handle different object creation scenarios. Understanding these advanced constructors is crucial for writing efficient, correct C++ code that properly manages resources and enables modern C++ features.
Copy Constructor
A copy constructor creates a new object as a copy of an existing object. It's called when objects are passed by value, returned by value, or explicitly copied.
Basic Copy Constructor Syntax
cpp
class String {
private:
char* data;
size_t length;
public:
// Regular constructor
String(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// Copy constructor
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
~String() {
deletedata;
}
const char* getData() const { return data; }
size_t getLength() const { return length; }
};
// Usage
String original("Hello");
String copy(original); // Copy constructor calledWhen Copy Constructor is Called
cpp
class Example {
public:
Example() { std::cout << "Default constructor" << std::endl; }
Example(const Example& other) { std::cout << "Copy constructor" << std::endl; }
};
void function(Example obj) { // Copy constructor called
// Function body
}
Example createObject() {
Example obj; // Default constructor
return obj; // Copy constructor called (or move constructor in C++11+)
}
// Usage
Example obj1; // Default constructor
Example obj2(obj1); // Copy constructor
Example obj3 = obj1; // Copy constructor
function(obj1); // Copy constructor (passing by value)
Example obj4 = createObject(); // Copy constructor (returning by value)Deep Copy vs Shallow Copy
cpp
class ResourceManager {
private:
int* data;
size_t size;
public:
ResourceManager(size_t s) : size(s) {
data = new int[size];
for (size_t i = 0; i < size; ++i) {
data[i] = 0;
}
}
// WRONG: Shallow copy (shares resources)
ResourceManager(const ResourceManager& other) {
data = other.data; // Both objects point to same memory!
size = other.size;
}
// CORRECT: Deep copy (separate resources)
ResourceManager(const ResourceManager& other) {
size = other.size;
data = new int[size];
for (size_t i = 0; i < size; ++i) {
data[i] = other.data[i];
}
}
~ResourceManager() {
deletedata;
}
};Move Constructor
A move constructor transfers ownership of resources from a source object, leaving the source in a valid but unspecified state. It's more efficient than copying because it avoids resource duplication.
Basic Move Constructor Syntax
cpp
class String {
private:
char* data;
size_t length;
public:
// Regular constructor
String(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// Copy constructor
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
// Move constructor
String(String&& other) noexcept {
data = other.data; // Transfer ownership
length = other.length;
other.data = nullptr; // Source no longer owns the data
other.length = 0;
}
~String() {
deletedata;
}
};
// Usage
String original("Hello");
String moved(std::move(original)); // Move constructor called
// original.data is now nullptr, original.length is 0When Move Constructor is Called
Move constructor is called on temporary objects who are about to go out of scope. We'll discuss move semantics more in the Low Latency C++ roadmap.
cpp
class Example {
public:
Example() { std::cout << "Default constructor" << std::endl; }
Example(const Example& other) { std::cout << "Copy constructor" << std::endl; }
Example(Example&& other) noexcept { std::cout << "Move constructor" << std::endl; }
};
Example createObject() {
Example obj;
return obj; // Move constructor called (C++11+)
}
// Usage
Example obj1;
Example obj2(std::move(obj1)); // Move constructor
Example obj3 = std::move(obj1); // Move constructor
Example obj4 = createObject();Delegating Constructor
A delegating constructor calls another constructor of the same class to avoid code duplication and share initialization logic.
Basic Delegating Constructor Syntax
cpp
class Rectangle {
private:
double width, height;
std::string color;
public:
// Primary constructor
Rectangle(double w, double h, const std::string& c)
: width(w), height(h), color(c) {}
// Delegating constructor - calls primary constructor
Rectangle(double w, double h)
: Rectangle(w, h, "white") {} // Delegates to primary constructor
// Delegating constructor - calls other delegating constructor
Rectangle(double size)
: Rectangle(size, size) {} // Delegates to two-parameter constructor
// Default constructor - delegates to primary constructor
Rectangle()
: Rectangle(1.0, 1.0, "white") {}
double getArea() const { return width * height; }
std::string getColor() const { return color; }
};
// Usage
Rectangle rect1(5.0, 3.0, "red"); // Primary constructor
Rectangle rect2(5.0, 3.0); // Delegating constructor
Rectangle rect3(5.0); // Delegating constructor
Rectangle rect4; // Default constructorDelegating Constructor with Inheritance
cpp
class Person {
protected:
std::string name;
int age;
public:
Person(const std::string& n, int a) : name(n), age(a) {}
Person(const std::string& n) : Person(n, 0) {}
Person() : Person("Unknown", 0) {}
};
class Student : public Person {
private:
std::string major;
public:
// Primary constructor
Student(const std::string& n, int a, const std::string& m)
: Person(n, a), major(m) {}
// Delegating constructor
Student(const std::string& n, int a)
: Student(n, a, "Undeclared") {}
// Delegating constructor
Student(const std::string& n)
: Student(n, 0) {}
// Delegating constructor
Student()
: Student("Unknown") {}
};Converting Constructor
A converting constructor is a constructor that can be called with a single argument of a different type, allowing implicit conversion from that type to the class type.
Basic Converting Constructor
cpp
class Complex {
private:
double real, imag;
public:
// Converting constructor from double
Complex(double r) : real(r), imag(0.0) {}
// Converting constructor from int
Complex(int r) : real(static_cast<double>(r)), imag(0.0) {}
// Regular constructor
Complex(double r, double i) : real(r), imag(i) {}
void display() const {
std::cout << real << " + " << imag << "i" << std::endl;
}
};
// Usage
Complex c1(3.14); // Converting constructor from double
Complex c2(42); // Converting constructor from int
Complex c3(1.0, 2.0); // Regular constructor
// Implicit conversions
Complex c4 = 5.0; // Converting constructor called
Complex c5 = 10; // Converting constructor calledPreventing Unwanted Conversions with explicit
cpp
class String {
private:
std::string data;
public:
// Regular constructor
String(const std::string& str) : data(str) {}
// Converting constructor - but explicit to prevent unwanted conversions
explicit String(int length) : data(length, ' ') {}
// Converting constructor - explicit to prevent unwanted conversions
explicit String(char c) : data(1, c) {}
const std::string& getData() const { return data; }
};
// Usage
String s1("Hello"); // Regular constructor
String s2(10); // Explicit converting constructor
String s3('A'); // Explicit converting constructor
// These would cause compilation errors without explicit:
// String s4 = 10; // ERROR: explicit constructor
// String s5 = 'A'; // ERROR: explicit constructorSummary
Advanced constructors provide powerful tools for object creation and resource management:
Copy Constructor:
- Purpose: Create deep copies of objects
- When called: Pass by value, return by value, explicit copying
- Best practice: Ensure deep copy to avoid resource sharing
Move Constructor:
- Purpose: Transfer resource ownership efficiently
- When called: std::move, rvalue references, temporary objects
- Best practice: Use noexcept and leave source in valid state
Delegating Constructor:
- Purpose: Avoid code duplication between constructors
- When to use: Multiple constructors with shared logic
- Best practice: Delegate to most complete constructor
Converting Constructor:
- Purpose: Enable implicit type conversion
- When to use: Natural type relationships
- Best practice: Use explicit to prevent unwanted conversions
Understanding these constructor types enables you to write efficient, correct C++ code that properly manages resources and leverages modern C++ features for optimal performance.
Questions
Q: What is the main purpose of a copy constructor?
A copy constructor creates a new object as a copy of an existing object. It's used when objects are passed by value, returned by value, or explicitly copied. The copy constructor should create a deep copy to avoid sharing resources between objects.
Q: When is a move constructor called instead of a copy constructor?
A move constructor is called when moving an object using std::move or when dealing with rvalue references. It's more efficient than copying because it can transfer ownership of resources instead of copying them, leaving the source object in a valid but unspecified state.
Q: What is the purpose of a delegating constructor?
A delegating constructor calls another constructor of the same class to avoid code duplication. It's useful when you have multiple constructors that share common initialization logic. The delegated constructor is called first, then the delegating constructor's body executes.
Q: What is a converting constructor?
A converting constructor is a constructor that can be called with a single argument of a different type, allowing implicit conversion from that type to the class type. It's useful for creating objects from different data types, but can sometimes lead to unexpected conversions.
Q: What is the Rule of Five in C++?
The Rule of Five states that if you define any of the copy constructor, copy assignment operator, move constructor, move assignment operator, or destructor, you should define all of them. This ensures proper resource management and prevents potential bugs.