Beyond the C++ Standard Library: An Introduction to Boost [Electronic resources] نسخه متنی

This is a Digital Library

With over 100,000 free electronic resource in Persian, Arabic and English

Beyond the C++ Standard Library: An Introduction to Boost [Electronic resources] - نسخه متنی

Bjorn Karlsson

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید

noncopyable


Header: "boost/utility.hpp"


The compiler is often a very good friend of the programmer, but not always. One example of its friendliness is the way that it automatically provides copy construction and assignment for our classes, should we decide not to do so ourselves. This can lead to some unpleasant surprises, if the class isn't meant to be copied (or assigned to) in the first place. When that's the case, we need to tell clients of this class explicitly that copy construction and assignment are prohibited. I'm not talking about comments in the code, but about denying access to the copy constructor and copy assignment operator. Fortunately, the compiler-generated copy constructor and copy assignment operator are not usable when the class has bases or data members that aren't copyable or assignable. boost::noncopyable works by prohibiting access to its copy constructor and assignment operator and then being used as a base class.

Usage


To make use of boost::noncopyable, have the noncopyable classes derive privately from it. Although public inheritance works, too, this is a bad practice. Public inheritance says IS-A (denoting that the derived class also IS-A base) to people reading the class declaration, but stating that a class IS-A noncopyable seems a bit far fetched. Include "boost/utility.hpp" when deriving from noncopyable.


#include "boost/utility.hpp"
class please_dont_make_copies : boost::noncopyable {};
int main() {
please_dont_make_copies d1;
please_dont_make_copies d2(d1);
please_dont_make_copies d3;
d3=d1;
}

The preceding example does not compile. The attempted copy construction of d2 fails because the copy constructor of noncopyable is private. The attempted assignment of d1 to d3 fails because the copy assignment operator of noncopyable is private. The compiler should give you something similar to the following output:


noncopyable.hpp: In copy constructor
' please_dont_make_copies::please_dont_make_copies
(const please_dont_make_copies&)':
boost/noncopyable.hpp:27: error: '
boost::noncopyable::noncopyable(const boost::noncopyable&)' is
private
noncopyable.cpp:8: error: within this context
boost/noncopyable.hpp: In member function 'please_dont_make_copies&
please_dont_make_copies::operator=(const please_dont_make_copies&)':
boost/noncopyable.hpp:28: error: 'const boost::noncopyable&
boost::noncopyable::operator=(const boost::noncopyable&)' is private
noncopyable.cpp:10: error: within this context

We'll examine how this works in the following sections. It's clear that copying an assignment is prohibited when deriving from noncopyable. This can also be achieved by defining the copy constructor and copy assignment operator privatelylet's see how to do that.

Making Classes Noncopyable


Consider again the class please_dont_make_copies, which, for some reason, should never be copied.


class please_dont_make_copies {
public:
void do_stuff() {
std::cout <<
"Dear client, would you please refrain from copying me?";
}
};

Because the compiler generates a copy constructor and an assignment operator, there's nothing about this class that prohibits copying or assignment.


please_dont_make_copies p1;
please_dont_make_copies p2(p1);
please_dont_make_copies p3;
p3=p2;

We could fix this mess by declaring the copy constructor and copy assignment operator private or protected, and by adding a default constructor (which would no longer be generated by the compiler).


class please_dont_make_copies {
public:
please_dont_make_copies() {}
void do_stuff() {
std::cout <<
"Dear client, would you please refrain from copying me?";
}
private:
please_dont_make_copies(const please_dont_make_copies&);
please_dont_make_copies& operator=
(const please_dont_make_copies&);
};

That works very well, but it isn't as immediately apparent to please_dont_make_copies' clients that it is noncopyable. Seeing noncopyable instead makes the class more obviously noncopyable with less typing.

Using noncopyable


The class boost::noncopyable is intended to be used as a private base class, which effectively turns off copy construction and copy assignment operations. Using the previous example, here's how the code would look when using noncopyable:


#include "boost/utility.hpp"
class please_dont_make_copies : boost::noncopyable {
public:
void do_stuff() {
std::cout << "Dear client, you just cannot copy me!";
}
};

There's no need to declare the copy constructor or copy assignment operator. Because we've derived from noncopyable, the compiler won't generate them either, which disables copying and copy assignment. Terseness can lend clarity, especially for such basic and distinct concepts such as this. For a client reading the code, it is immediately apparent that this class cannot be copied, or copy assigned, because boost::noncopyable appears at the very start of the class definition. One last note: Do you recall that the default access control for classes is private? That means that inheritance is private by default, too. You could make this fact even more obvious by spelling it out like this:


class please_dont_make_copies : private boost::noncopyable {

It all depends on the audience; some programmers find such redundant information annoying and distracting, whereas others appreciate the clarification. It's up to you to decide which way is right for your classes, and your programmers. Either way, using noncopyable is definitely better than "forgetting" the copy constructor and the copy assignment operator, and it's also clearer than privately declaring them.

Remember the Big Three


As we have seen, noncopyable provides a convenient way of disabling copying and copy assignment for a class. But when do we need to do that? Which are the circumstances that demand a user-defined copy constructor or copy assignment operator in the first place? There is a general answer to this question, one that just about always is correct: Whenever you need to define one of the destructor, the copy constructor, or the copy assignment operator, you also need to define the remaining two.[5] These three interoperate in important ways, and when one exists, the others typically must, too. Let's assume that one of your classes has a member that is a pointer. You have defined a destructor for proper deallocation, but you haven't bothered defining a copy constructor or a copy assignment operator. This means that there are at least two potential defects in your code, which are easy to trigger.

[5] The name Law of the Big Three comes from C++ FAQs (see [2] in the Bibliography for details).


class full_of_errors {
int* value_;
public:
full_of_errors() {
value_=new int(13);
}
~full_of_errors() {
delete value_;
}
};

Using this class, there are at least three ways of producing errors that aren't obvious if one neglects to consider the copy constructor and the assignment operator that the compiler has graciously augmented the class with.


full_of_errors f1;
full_of_errors f2(f1);
full_of_errors f3=f2;
full_of_errors f4;
f4=f3;

Note that the two equivalent ways of invoking the copy constructor here are on the second and third lines. They both call the synthesized copy constructor, although the syntax is different. The final error is on the last line, where the copy assignment operator makes sure that the same pointer is used and deleted by at least two instances of full_of_errors. Doing things correctly, we would have realized the need for copy assignment and copy construction right away, when we defined our destructor. Here's what should have been done:


class not_full_of_errors {
int* value_;
public:
not_full_of_errors() {
value_=new int(13);
}
not_full_of_errors(const not_full_of_errors& other) :
value_(new int(*other.value_)) {}
not_full_of_errors& operator=
(const not_full_of_errors& other) {
*value_=*other.value_;
return *this;
}
~not_full_of_errors() {
delete value_;
}
};

So, whenever one of the big threecopy constructor, (virtual) destructor, and copy assignment operatoris manually defined in a class, think long and hard before deciding that the remaining two are unnecessary. And, remember to use boost::noncopyable if there is to be no copying at all!

Summary


There are many types for which we need to prohibit copying and copy assignment. However, declaring the copy constructor and copy assignment operator private is often neglected for such types, and responsibility for knowing that copying doesn't make sense is transferred to clients of the type. Even when types ensure that they cannot be copied or assigned, using private copy constructors and copy assignment operators, it isn't always clear to the client that this is the case. Of course, the compiler kindly informs those who try, but it may not be apparent where the error is coming from. Either way, the best we can do is to be explicit about it, and deriving from noncopyable makes a clear statement. It is immediately in view when scanning the declaration of the type. When compiling, an error message almost certainly includes the name noncopyable. And it also saves some typing, which is a killer argument for some.

Use noncopyable when:

Copying and copy assignment of types is not allowed

Prohibition of copying and assignment should be as explicit as possible

/ 124