What is the difference between a shallow and a deep copy of a class?
Answer
Shallow copying is faster, but it's "lazy" in how it handles pointers and references. Instead of making a fresh copy of the actual data the pointer points to, it just copies over the pointer value. So, both the original and the copy will have pointers that reference the same underlying data. (Ditto for references.)
Deep copying actually clones the underlying data; it's not shared between the original and the copy.
Let's get specific. Here's a class representing a bus:
class Bus
{
private:
int yearBuilt_;
string name_;
vector<string>* routeList_;
public:
Bus(int yearBuilt, const string& name, vector<string>& routeList) :
yearBuilt_(yearBuilt),
name_(name),
routeList_(&routeList)
{
}
void setName(const string& name);
void addStop(const string& stopName);
void removeStop(const string& stopName);
void busDetails() const;
};
Pay close attention to our routeList_ variable. We've made it a pointer so that it can be updated remotely (in case there's a detour). We've also included methods to manipulate the list of stops and print out the bus information.
What do you think happens when we run this code?
void test()
{
vector<string>* route = new vector<string>();
// Add one bus to our fleet
Bus local(2015, "LOCAL", *route);
local.addStop("Main St.");
local.addStop("First Ave.");
local.addStop("Second Ave.");
// Add another bus that skips First Ave.
Bus express = local;
express.setName("EXPRESS");
express.removeStop("First Ave.");
// Let's see what we've got
local.busDetails();
express.busDetails();
}
Here's the output:
The LOCAL bus was built in 2015 and its current
route is: Main St., Second Ave.
The EXPRESS bus was built in 2015 and its current
route is: Main St., Second Ave.
Oops. Looks like the local and express bus were both sharing the same route list. Like this:
Okay, so when we created the express bus (Bus express = local), we made a shallow copy, where both objects ended up with a pointer to the same route list.
We want the express to be a deep copy of the local bus, so that they each have their own route list.
How can we get deep copies?
Side note: Although learning the answers to these common C++ questions is important, it's so much more important to be able to quickly solve C++ problems you've never seen before. Interviewers want to see that you can do more than just memorize facts!
If you really want to take your prep to the next level, and learn the right way of thinking to quickly solve new problems, check out our free 7-day email course:
Cool, watch your inbox!
Well, what happens when we run this line of code:
Bus express = local;
In C++, there are two functions that copy the values from one object to another: copy constructors and copy assignment operators.
The copy constructor gets called whenever we are constructing a new object from an existing one. So, this declaration uses copy constructors:
Bus express(local);
And, this code block also uses the copy constructor (even though there's an equals sign):
Bus express = local;
The copy assignment operator gets called when we're updating an existing object:
Bus local;
Bus express;
express = local;
Let's look back at the line where we created our express bus. Would it call the copy constructor or the copy assignment operator?
Bus express = local;
Since we're creating the express bus, we'll use the copy constructor here.
Wait a second though. We didn't ever define a copy constructor! Where's the function that's getting called?
When writing a class, defining a copy constructor and a copy assignment operator is optional. If you don't specify one, then the C++ compiler will generate it for you, filling in a method that makes shallow copies.
If we want deep copies, we can get them be defining a copy constructor and assignment operator for our Bus class. Here's what our copy constructor could look like:
Bus::Bus(const Bus& src) :
yearBuilt_(src.yearBuilt_),
name_(src.name_),
routeList_(new vector<string>(*src.routeList_))
{
}
The line of code: routeList_(new vector<string>(*src.routeList_)) creates new space so that the new Bus object can have its own route list.
Now, when we create our express bus, it'll call the copy constructor we just wrote:
Bus express = local;
Under the hood, our buses will look like this:
That covers the copy constructor. What about the copy assignment operator?
We probably want to define that too. Otherwise, we'll get deep copies whenever we use the copy constructor but shallow copies whenever we use the copy assignment operator. That sounds like a surefire way to get some bugs.
So, let's write out a copy assignment operator that will make deep copies. It'll look a lot like the copy constructor, except we'll want to clean up the existing object before we start assigning new values. Like this:
Bus& Bus::operator=(const Bus& src)
{
if (this != &src) {
// Clean up the existing routeList_
delete routeList_;
// Create a deep copy of the values from src
yearBuilt_ = src.yearBuilt_;
name_ = src.name_;
routeList_ = new vector<string>(*src.routeList_);
}
return *this;
}
One last thing to note: copy constructors don't have to create deep copies. Sometimes, we want a separate copy of some of the class members and a shared reference for others.
For instance, if we add information about the bus supervisor, we'll probably want the same supervisor for all buses. So, we can let them share a reference to the supervisor name, while creating a separate copy of the route list:
Bus::Bus(int yearBuilt, const string& name, vector<string>& routeList, const string& supervisor) :
yearBuilt_(yearBuilt),
name_(name),
routeList_(&routeList),
supervisor_(&supervisor)
{
}
Bus::Bus(const Bus& src) :
yearBuilt_(src.yearBuilt_),
name_(src.name_),
routeList_(new vector<string>(*src.routeList_)), // New bus gets its own copy of the route list
supervisor_(src.supervisor_) // But shares the same supervisor
{
}
What is a template function?
Answer
C++ makes us specify the types for function arguments and return values. Templates let us write functions that take in any type. This helps us keep our code DRY.
For example, let’s look at this swap function. Sometimes, we need to swap two integers:
void mySwap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
But what if we wanted to swap two chars?
void mySwap(char& x, char& y)
{
char tmp = x;
x = y;
y = tmp;
}
Or two strings?
void mySwap(string& x, string& y)
{
string tmp = x;
x = y;
y = tmp;
}
Aside from the types, these functions are exactly the same! That's annoying—it means that our code is harder to read and there are more places for us to introduce bugs. It would be nice if we could write one function to swap two things of the same type that we could use instead of these three versions.
Template functions to the rescue!
Using templates, we can combine all three swap functions above, like this:
template <typename T>
void mySwap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
};
So now we can write code like this:
int a = 1, b = 2;
mySwap(a, b);
cout << "a=" << a << ", b=" << b << endl; // "a=2, b=1"
string c = "one", d = "two";
swap(c, d);
cout << "c=" << c << ", d=" << d << endl; // "c=two, d=one"
Careful: C++ templates can make programs "bloated" by increasing the amount of code that needs to be compiled. Remember the three different swap functions we created earlier? Behind the scenes, the C++ compiler is generating all three of those functions: one for integers, one for strings, and one for characters. Using templates saves us time and makes our code shorter, but we're definitely not saving any space.
What is the Diamond problem? How can we get around it?
The diamond problem is a common question used to test your understanding of multiple inheritance.
Answer
The diamond problem refers to an issue when a base class is inherited by two subclasses, and both subclasses are inherited by a fourth class. When this happens, we need to give the compiler a bit of guidance about the exact structure of inheritance we want.
Let’s use the example of lions, tigers, and ... ligers:
We'll start with a base class, Mammal, with the method eat():
class Mammal
{
protected:
const string name_;
public:
Mammal(const string& name);
void eat()
{
cout << "This Mammal " << name_ << " is eating" << endl;
}
};
Lion and Tiger both inherit from Mammal:
class Tiger : public Mammal
{
public:
void groom();
};
class Lion : public Mammal
{
public:
void groom();
};
Finally, Liger inherits from both Lion and Tiger:
class Liger : public Tiger, public Lion
{
public:
void nap();
};
This is the inheritance structure we're going for. (See how it looks like a diamond!)
Satisfied with our structure so far, we decide to write some code using the Liger class:
Liger Lyle("Lyle");
Lyle.groom();
Lyle.eat();
But, when we compile it, we get this error message:
$ g++ ligers.cpp
error: non-static member 'eat' found in multiple
base-class sub-objects of type 'Mammal':
class Liger -> class Tiger -> class Mammal
class Liger -> class Lion -> class Mammal
Lyle.eat();
What's happening here?
The compiler is complaining that the Liger class has two versions of the eat method—one from the Tiger class (which inherited it from the Mammal class) and one from the Lion class (which also inherited it from the Mammal class). So, the compiler sees two different eat methods, and it doesn't know which version it should use for Ligers.
This happens because C++ doesn't recognize that the Lion and Tiger classes are inheriting from the same Mammal class. What it sees instead is something like this:
We can ensure that the compiler inherits the same Mammal class into the Lion and Tiger classes with the virtual keyword, like this:
class Tiger : virtual public Mammal
{
public:
void groom();
};
class Lion : virtual public Mammal
{
public:
void groom();
};
When a base class is virtually inherited, the C++ compiler makes sure that this class is created only once. That means that all subclasses, like Lion and Tiger, will lead back to the same Mammal base class, giving us the diamond structure we've wanted all along.
Note that we have to put the virtual keyword in the definition of both Tiger and Lion. If we don't then we'll get the incorrect structure we had before.
The virtual keyword can also be used with class methods. When a class method is declared virtual, it can be overridden by inheriting classes.
Going back to our example with animals, we might want to let different mammals define their own eat() method. Since we'll be overriding the eat method in inheriting classes, we need to declare it as virtual, like this:
class Mammal
{
protected:
const string name_;
public:
Mammal(const string& name);
virtual void eat()
{
cout << "This Mammal " << name_ << " is eating" << endl;
}
};
When developing classes, most public or protected methods will be declared virtual so that inheriting classes can override them as needed. In fact, the only method that can't be declared virtual is the class constructor. (Clearly, we don't want others to override that!)
Why are destructors important?
Answer
Destructors are important because they give us a chance to free up an object's allocated resources before the object goes out of scope. Since C++ doesn't have a garbage collector, resources that we don't free ourselves will never be released back to the system, eventually making things grind to a halt.
A resource leak is when a program finishes using some resource (allocated memory, open files, network sockets) but doesn’t properly release it, hoarding resources it's not using any more. Mozilla was famous for having memory leaks with Firefox: the browser would cache recently visited sites so that users could quickly go back to them, but it wouldn't delete saved sites. Over time, the amount of data saved would get pretty large, slowing down the browser.
We're focusing on C++98. We should note, though, that newer versions of C++ have introduced "smart pointers," which are automatically freed when they go out of scope. If you can use smart pointers, they're generally preferable since you can avoid some of the headaches of manual memory management. That said, many projects still use manual memory management, so it's important to understand destructors.
To make this concrete, let's look at an example: shopping carts. Suppose our shopping cart class keeps track of its items in a dynamically allocated vector, like this:
class ShoppingCart
{
private:
vector<string>* items_;
public:
ShoppingCart() :
items_(new vector<string>())
{
}
~ShoppingCart()
{
// Need to free up items here
}
void addItem(const string& item)
{
items_->push_back(item);
}
};
A destructor method is declared with the same name as the constructor, with a ~ in front. For our ShoppingCart class, that's ~ShoppingCart. The C++ compiler exclusively reserves this syntax for destructors, so it's important to not use this name for anything else.
The items vector is dynamically allocated and could take up a lot of space, so we need to free it when we're done. How do we do that?
We can just call the vector's destructor!
Careful, though! We don't call destructors directly.
- If we allocated an object dynamically (using new), then we call the object's destructor using the delete keyword.
-
For objects on the
stack
↴
(not allocated using new), the destructor will be called automatically once it is out of scope.
Quick reference
Worst Case space O(n) push O(1) pop O(1) peek O(1) A stack stores items in a last-in, first-out (LIFO) order.
Picture a pile of dirty plates in your sink. As you add more plates, you bury the old ones further down. When you take a plate off the top to wash it, you're taking the last plate you put in. "Last in, first out."
Strengths:
- Fast operations. All stack operations take O(1) time.
Uses:
- The call stack is a stack that tracks function calls in a program. When a function returns, which function do we "pop" back to? The last one that "pushed" a function call.
- Depth-first search uses a stack (sometimes the call stack) to keep track of which nodes to visit next.
- String parsing—stacks turn out to be useful for several types of string parsing.
Implementation
You can implement a stack with either a linked list or a dynamic array—they both work pretty well:
Stack Push Stack Pop Linked Lists insert at head remove at head Dynamic Arrays append remove last element
Let's see how this works if we have a few shopping carts.
{
ShoppingCart cart;
cart.addItem("eggs");
cart.addItem("cheese");
}
// When cart goes out of scope here,
// destructor ShoppingCart::~ShoppingCart() is called automatically
This shopping cart is allocated on the stack, so its destructor gets called automatically once it goes out of scope.
ShoppingCart* cart = new ShoppingCart();
cart->addItem("milk");
cart->addItem("bread");
delete cart; // calls destructor ShoppingCart::~ShoppingCart() for cart
This shopping cart is dynamically allocated, so we have to manually call its destructor when we're done with it.
Okay, but what should actually go in the destructor for ShoppingCart? Since we dynamically allocated the items vector, we need to use the delete keyword to call its destructor. We'd do that in ~ShoppingCart, like this:
ShoppingCart::~ShoppingCart()
{
delete items_;
}
The nice thing about destructors is they centralize object cleanup in one place. As we make the ShoppingCart class more complex, we can always make sure we're not leaking resources by updating our destructor to do any necessary cleanup. That's much easier to maintain than having cleanup code scattered around our code base.
Pro tip: Learn how to avoid the dreaded "whiteboard freeze." In our 7 day email crash course, we'll give you the strategy for quickly breaking down and solving any coding interview question.
Cool, watch your inbox!