Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Copy Assignment Operator Overloading
Handle object assignment with proper resource management.
Overloading the assignment operator
The copy assignment operator (operator=) copies values from one existing object to another existing object. Understanding the difference between copy construction and copy assignment is essential for effective C++ programming.
Copy assignment vs copy constructor
The copy constructor and copy assignment operator serve similar purposes but are used in different situations:
Inventory original{100};
Inventory copy1{original}; // Copy constructor: creates new object
Inventory copy2 = original; // Copy constructor: creates new object (not assignment!)
Inventory existing{50};
existing = original; // Copy assignment: existing object modified
Key difference: If a new object must be created, the copy constructor is used. If no new object is created, the assignment operator is used.
Overloading the assignment operator
The assignment operator must be overloaded as a member function. Here's a simple Inventory class:
#include <iostream>
class Inventory
{
private:
int m_itemCount{};
public:
explicit Inventory(int count = 0) : m_itemCount{count} {}
Inventory& operator=(const Inventory& inv);
friend std::ostream& operator<<(std::ostream& out, const Inventory& inv);
};
std::ostream& operator<<(std::ostream& out, const Inventory& inv)
{
out << inv.m_itemCount << " items";
return out;
}
Inventory& Inventory::operator=(const Inventory& inv)
{
m_itemCount = inv.m_itemCount;
return *this;
}
int main()
{
Inventory warehouse1{250};
Inventory warehouse2{};
warehouse2 = warehouse1; // Calls overloaded assignment
std::cout << "Warehouse 2: " << warehouse2 << '\n';
return 0;
}
We return *this to allow assignment chaining:
inv1 = inv2 = inv3; // Right-to-left evaluation
The self-assignment problem
C++ allows self-assignment:
Inventory stock{100};
stock = stock; // Self-assignment!
While harmless for simple classes, self-assignment can be dangerous for classes managing resources. Consider this Buffer class with dynamic memory:
#include <iostream>
#include <cstring>
class Buffer
{
private:
char* m_data{};
std::size_t m_size{};
public:
Buffer(const char* data = "")
{
if (data)
{
m_size = std::strlen(data);
m_data = new char[m_size + 1];
std::strcpy(m_data, data);
}
}
~Buffer()
{
delete[] m_data;
}
Buffer& operator=(const Buffer& buf);
friend std::ostream& operator<<(std::ostream& out, const Buffer& buf);
};
// Dangerous implementation without self-assignment check!
Buffer& Buffer::operator=(const Buffer& buf)
{
if (m_data)
delete[] m_data;
m_size = buf.m_size;
m_data = nullptr;
if (buf.m_data)
{
m_data = new char[m_size + 1];
std::strcpy(m_data, buf.m_data);
}
return *this;
}
std::ostream& operator<<(std::ostream& out, const Buffer& buf)
{
out << (buf.m_data ? buf.m_data : "(empty)");
return out;
}
What happens during self-assignment (buffer = buffer)?
- We delete
m_data - But
buf.m_datais the same asm_data- we just deleted it! - We try to copy from deleted memory - undefined behavior!
Detecting and handling self-assignment
Add a self-assignment check:
Buffer& Buffer::operator=(const Buffer& buf)
{
if (this == &buf) // Self-assignment check
return *this;
if (m_data)
delete[] m_data;
m_size = buf.m_size;
m_data = nullptr;
if (buf.m_data)
{
m_data = new char[m_size + 1];
std::strcpy(m_data, buf.m_data);
}
return *this;
}
By comparing the address of the implicit object (this) with the address of the parameter (&buf), we can detect self-assignment and return immediately.
When to omit the self-assignment check
For classes that naturally handle self-assignment correctly, the check can be omitted:
class Inventory
{
private:
int m_itemCount{};
public:
Inventory& operator=(const Inventory& inv)
{
// No self-assignment check needed
m_itemCount = inv.m_itemCount; // Works correctly even if inv is *this
return *this;
}
};
However, it's generally safer to include the check. The performance cost is minimal (a single pointer comparison), and it prevents potential bugs.
Include a self-assignment check in your copy assignment operators unless the class naturally handles self-assignment correctly.
The implicit copy assignment operator
If you don't provide a copy assignment operator, the compiler generates one that does memberwise assignment:
class Inventory
{
private:
int m_itemCount{};
int m_maxCapacity{};
public:
// Compiler generates this:
// Inventory& operator=(const Inventory& inv)
// {
// m_itemCount = inv.m_itemCount;
// m_maxCapacity = inv.m_maxCapacity;
// return *this;
// }
};
For simple classes without resource management, the implicit version works fine.
Deleting the copy assignment operator
You can prevent assignment using = delete:
class UniqueResource
{
private:
int m_id{};
public:
UniqueResource(int id) : m_id{id} {}
UniqueResource& operator=(const UniqueResource&) = delete;
};
int main()
{
UniqueResource res1{1};
UniqueResource res2{2};
// res1 = res2; // Compilation error!
return 0;
}
Note: If your class has const members, the compiler automatically deletes the copy assignment operator (since const members can't be assigned).
Summary
Copy assignment operator (operator=): Copies values from one existing object to another existing object, different from the copy constructor which creates new objects.
When it's called: Only when assigning to an already-existing object. If a new object is created (including Type obj = other), the copy constructor is used instead.
Must be member function: The assignment operator must be overloaded as a member function and should return *this by reference to enable assignment chaining.
Self-assignment problem: Classes managing resources (like dynamic memory) can have undefined behavior during self-assignment if they delete resources before copying. Add a self-assignment check by comparing this == &other.
When to omit self-assignment check: For simple classes that naturally handle self-assignment correctly (like those with only primitive members), the check can be omitted, though including it is safer.
Implicit copy assignment: If you don't provide one, the compiler generates a memberwise assignment operator. This works for simple classes but fails for classes managing resources.
Deleting copy assignment: Use operator= = delete to prevent assignment. The compiler automatically deletes it for classes with const members.
Best practice: Include self-assignment checks for classes managing resources, return *this by reference for chaining, and only define custom assignment operators when the default memberwise assignment is insufficient.
The copy assignment operator is essential for classes managing resources, requiring careful handling of self-assignment and resource cleanup to avoid undefined behavior.
Copy Assignment Operator Overloading - Quiz
Test your understanding of the lesson.
Practice Exercises
String Wrapper with Safe Assignment
Create a StringWrapper class that manages a dynamically allocated C-style string. Implement a proper copy assignment operator with self-assignment checking to prevent resource issues.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!