"Victor Bazarov" <v.********@comAcast.net> wrote in message
news:H7********************@comcast.com...
ma740988 wrote:
See "the Rule of Three".
The rule of three can be understood, as I've done in the past, as "don't
define operator= if the class consists only of members that take care of
themselves."
With "taking care of themselves," I mean classes that define proper copy
constructors and assignment operators, which work correctly. Further, let's
take "working correctly" here as copying and assigning successfully.
Propagating exceptions should be fine too, if the object is left in a
consistent state. For example, std::string is such a class.
If one applies the guidelines as above, and believes that their class will
work correctly even if a member's operator= can throw, they might be dealing
with corrupt objects.
I think that the rule of three must go with an addition:
- Observe the rule of three (If any one of the three special functions needs
to be defined, chances are you will have to at least declare the other two)
- Additionally, if you want your objects be in a consistent state if a
member throws, always define operator=.
I've run a simple program to analyze what happens during the execution of
operator= when different operations throw or not and when operator= is
implicit or explicit.
A: a class without any members
B: a class that contains two std::strings and an A that is deliberately
defined between two strings
class B
{
string first_;
A a_;
string last_;
/* ... */
};
The legend for the table below:
completes: The operation completes successfully
throws: The operation is interrupted with an exception
implicit: The compiler generated operator= is used
explicit: operator= is defined by the user
new: The object gets the new state
old: The object is left in the old state
corrupt: The state of the object is corrupt
The results are:
A::A(A) A::operator=(A) B::operator=(B) | B's state
------------------------------------------------------|------------
completes completes implicit | new
throws completes implicit | new
completes throws implicit | corrupt
throws throws implicit | corrupt
completes completes explicit | new
throws completes explicit | old
completes throws explicit | new
throws throws explicit | old
What I digest from these results is:
1) If you leave operator= to the compiler (implicit) you may end up with a
corrupt object if operator= of a member throws
2) If you define operator= yourself (explicit) you may be left with the old
state
3) Otherwise you will get the new state
As a result, operator= is even more special that the other two, in that, it
prevents having an inconsistent object.
Here is the program I used to test these:
#include <iostream>
#include <string>
using namespace std;
// Comment-out or keep combinations of these macros to see the
// behavior for each case
// #define A_COPY_THROWS
// #define A_ASSIGNMENT_THROWS
// #define B_ASSIGNMENT_EXPLICIT
class A
{
public:
A()
{}
#if defined(A_COPY_THROWS)
A(A const &)
{
throw 42;
}
#endif
#if defined(A_ASSIGNMENT_THROWS)
A & operator= (A const &)
{
throw 42;
}
#endif
void swap(A & other)
{}
};
class B
{
string first_;
A a_;
string last_;
friend ostream & operator<< (ostream &, B const &);
public:
explicit B(string const & value)
:
first_(value),
last_(value)
{}
bool operator== (B const & other) const
{
return ((first_ == other.first_)
&&
(last_ == other.last_));
}
#if defined(B_ASSIGNMENT_EXPLICIT)
B & operator= (B temp)
{
swap(temp);
return *this;
}
#endif
void swap(B & other)
{
first_.swap(other.first_);
a_.swap(other.a_);
last_.swap(other.last_);
}
};
ostream & operator<< (ostream & os, B const & b)
{
return os << b.first_ << ' ' << b.last_;
}
bool check_B_invariant(string const & label, B const & lhs, B const & rhs)
{
cout << label << ": ";
cout << "comparing " << lhs << " with " << rhs << '\n';
if ( ! (lhs == rhs))
{
cout << "NO ";
}
cout << "match\n";
}
int main()
{
B zero("0");
B b0("0");
B b1("1");
check_B_invariant("before", zero, b0);
try
{
b0 = b1;
check_B_invariant("completed", b0, b1);
}
catch (...)
{
check_B_invariant("caught exception", b0, zero);
}
}
Ali