I recently attended an interview for a C++ position in a very reputed bank.
The interviewer started off by asking me to implement a string class but was only interested in constructor, copy-constructor, assignment operator and destructor.
Basically, he wanted to know how I would implement Rule of Three and nothing else. He was not even interested in move operations.
So, I implemented the assignment operator in terms of copy constructor:
class String
{
/*....*/
String& operator=(String other)
{
std::swap(m_data, other.m_data);
return *this;
}
private:
char* m_data;
};
Since this is pretty common way to implement assignment operator, I thought he would like it. The problem started with that.
He questioned why I implement it that way. When I explained that this is exception safe way of implementing it, he asked what's wrong in implementing it:
String& operator=(const String& other)
{
if (this != &other)
{
delete[] m_data;
m_data = new char[strlen(other.m_data) + 1] {'0円'};
strcpy(m_data, other.m_data);
return *this;
}
}
His argument was:
We are about to overwrite the data anyways so what's wrong in deleting first even if it's not exception safe?
In the moment it did not occur to me that code will crash in the event of an exception but I did try to argue that internal data should retain original values in the event of an exception.
Had he argued on the basis that copy-and-swap will always do reallocation even if there is enough size, etc, I would have felt happy. But his argument was that he wants performance at any cost.
Can somebody help me with a comparative review of these two operator=
implementations so that I am in a better position to argue my case / understand the interviewer's position if I encounter similar situations in the future?
-
5\$\begingroup\$ If you are after a comparative review of the implementations, then this question is on topic, if you are seeking advice about how to deal with conflict in interview situations, I think it's probably better suited to programmers.stackexchange (where similar questions have probably been asked). \$\endgroup\$forsvarir– forsvarir2016年07月28日 12:28:57 +00:00Commented Jul 28, 2016 at 12:28
-
\$\begingroup\$ I've attempted to reword your question so that it would be on-topic for this site. It's not exactly what you originally asked (which I still think is more suited to programmers), but it may be useful for you going forward. If you like the edit, I will try to get the question reopened. If not, feel free to roll it back. \$\endgroup\$forsvarir– forsvarir2016年07月28日 20:05:33 +00:00Commented Jul 28, 2016 at 20:05
-
\$\begingroup\$ @forsvarir I am fine with the edit. \$\endgroup\$Jagannath– Jagannath2016年07月28日 22:31:51 +00:00Commented Jul 28, 2016 at 22:31
-
\$\begingroup\$ The only potential issue I have here is that it could become a his code/my code situation, which we do not allow. \$\endgroup\$user34073– user340732016年07月29日 01:06:23 +00:00Commented Jul 29, 2016 at 1:06
-
\$\begingroup\$ No worries. I'm fine if it stays closed.As I mentioned in my question,his code has double delete issue in the event of an exception.it was my bad luck to face such an interview. \$\endgroup\$Jagannath– Jagannath2016年07月29日 02:20:02 +00:00Commented Jul 29, 2016 at 2:20
1 Answer 1
Your version
String& operator=(String other)
{
std::swap(m_data, other.m_data);
return *this;
}
is
- Exception safe
- Correct (assuming that copy and move constructors are implemented correctly)
- Fairly efficient in C++11 if the move constructor is implemented
- Potentially slightly less efficient than implementing separate copy & move constructors
The interviewer's version
String& operator=(const String& other)
{
if (this != &other)
{
delete[] m_data;
m_data = new char[strlen(other.m_data) + 1] {'0円'};
strcpy(m_data, other.m_data);
return *this;
}
}
is terrible, for the following reasons:
- If allocation fails,
m_data
becomes a dangling pointer and destruction (or another assign) will lead to a double delete. - If
this == &other
it doesn't return a value and therefore leads to undefined behavior (again). - Will fail if
other.m_data == 0
(this might be ok if null strings are disallowed, but that would make even weak exception safety complicated) - Less seriously, it might lead to the content of
m_data
being initialized twice (depending on the optimizer and it's knowledge ofstrcpy
) - Cannot be extended to support C++11 by implementing only an additional move constructor.
Furthermore, the style could be improved as well:
- By using an early return instead of a long indented block
- By not using C-style strings (and having a length member instead, although your version is presumably implemented in the same way as only
m_data
is swapped) strlen
andstrcpy
are not prefixed withstd::
which they should be in C++ (havingstrlen
andstrcpy
in the global namespace is allowed but not required by the standard.
A fixed version (still using a C-style representation) might look as follows:
String& operator=(const String& other)
{
if(this == &other)
return *this;
delete[] m_data;
m_data = 0; // weak exception safety, which might be sufficient, but only if null strings are allowed, complicating the following code
if(!other.m_data) // strlen will fail if other.m_data == 0
return *this;
std::size_t length = std::strlen(other.m_data);
m_data = new char[length + 1];
std::memcpy(m_data, other.m_data, length + 1);
return *this;
}
-
\$\begingroup\$ Thanks. The fixed version is good with the early return if
other.m_data == 0
. \$\endgroup\$Jagannath– Jagannath2016年07月29日 22:34:55 +00:00Commented Jul 29, 2016 at 22:34 -
\$\begingroup\$ With regards to this
other.m_data == 0
, Empty string != null string, so can be represented by a 1 char string which consists of only a null terminator, so perhaps you mean null strings in your explanation? I'm not sure why checking for null on construction / assignment fromchar*
would cause overly complicated code. \$\endgroup\$forsvarir– forsvarir2016年07月30日 11:42:31 +00:00Commented Jul 30, 2016 at 11:42 -
\$\begingroup\$ @forsavir yes I meant null strings in point 3. if allocation fails after delete you might not even be able to allocate a 1 char string, so you'd have to work with a static allocation for the empty string (as you would otherwise lose even weak exception safety) \$\endgroup\$Joe– Joe2016年07月30日 11:49:20 +00:00Commented Jul 30, 2016 at 11:49
-
\$\begingroup\$ I'd suggest using
nullptr
rather than 0 in the linem_data = 0;
. Also, since you already have the length, you could use the somewhat fasterstd::memcpy
instead ofstd::strcpy
to copy both the string and the terminatingNUL
character. \$\endgroup\$Edward– Edward2016年07月30日 12:32:15 +00:00Commented Jul 30, 2016 at 12:32 -
\$\begingroup\$ @Edward as the question mentions the rule of three I assumed C++98 and therefore didn't use nullptr. You are correct about the memcpy optimization. \$\endgroup\$Joe– Joe2016年07月30日 12:35:11 +00:00Commented Jul 30, 2016 at 12:35
Explore related questions
See similar questions with these tags.