|
|
|
Sponsored Link •
|
|
Advertisement
|
It should be noted that there are times when copy construction and copy assignment do not make sense for a class. In those cases, it's trivial to disable them by using the common idiom of declaring the copy constructor and copy assignment operator private, as in:
class SelfishBeastie
{
. . .
private:
SelfishBeastie(const SelfishBeastie&);
SelfishBeastie& operator=(const SelfishBeastie&);
};
An alternative is to use existing code; there's a class called
boost::noncopyable [5] in the Boost libraries;
inheriting from that class is nice as it documents that the
class does not support copying and assignment (at least for
all who are familiar with noncopyable!).
class SelfishBeastie
: boost::noncopyable
{
. . .
Another way of prohibiting copy construction and copy
assignment is to make one or more members a reference or
const (or const reference, for the
especially cautious)—this effectively shuts down the
compiler's ability to generate these special member functions.
As Matthew discusses in detail in Imperfect C++ [6], this is not the
preferred way of denoting that a class is not copyable, since
it fails to adequately communicate the class's design to its
users. It is, however, an excellent way to enforce design
decisions; hence, it is a mechanism for communicating the
original design decisions to future maintainers of the class,
rather than a way of documenting semantics to the class's
users. (Of course, with this technique all constructors of the
class will need to initialize the reference members (as
opposed to overwriting them within the constructor body),
which is itself a good thing.)
Although you've now come quite far in making the class
Example behave properly, it's easy to go astray
when exceptions come into play. Let's add another pointer to
SomeResource in our Example class,
like so:
class Example {
SomeResource* p_;
SomeResource* p2_;
public:
Example() :
p_(new SomeResource()),
p2_(new SomeResource()) {
std::cout << "Creating Example, allocating SomeResource!\n";
}
Example(const Example& other) :
p_(new SomeResource(*other.p_)),
p2_(new SomeResource(*other.p2_)) {}
Example& operator=(const Example& other) {
// Self assignment?
if (this==&other)
return *this;
*p_=*other.p_;
*p2_=*other.p2_;
return *this;
}
~Example() {
std::cout << "Deleting Example, freeing SomeResource!\n";
delete p_;
delete p2_;
}
};
Now consider what happens when an instance of Example is
being constructed, and the second instance of
SomeResource (pointed to by p2_)
throws upon construction. One SomeResource,
pointed to by p_, will already have been
allocated, but still, the destructor will not be called! The
reason is that from the compiler's point of view, the instance
of Example has never existed, because the
constructor never completed. This unfortunately means that
Example is not exception-safe, due to a potential
resource leak.
To make it safe, one practicable alternative is to move the initialization outside of the ctor-initializer, like so:
Example() : p_(0),p2_(0)
{
try {
p_=new SomeResource();
p2_=new SomeResource("H",true);
std::cout << "Creating Example, allocating SomeResource!\n";
}
catch(...) {
delete p2_;
delete p_;
throw;
}
}
Although you've managed to solve the issue of exception safety for the moment, this is not a very appealing solution, since we C++ programmers are weaned on initialization over assignment. As you'll soon see, an old and trustworthy technique comes to the rescue.
|
Sponsored Links
|