The C++11 rules governing the special member functions are thus:
• Default constructor: Same rules as C++98. Generated only if the class contains no user-declared constructors.
• Destructor: Essentially same rules as C++98; sole difference is that destructors are noexcept by default (see Item 14). As in C++98, virtual only if a base class destructor is virtual.
• Copy constructor: Same runtime behavior as C++98: memberwise copy construction of non-static data members. Generated only if the class lacks a user-declared copy constructor. Deleted if the class declares a move operation. Generation of this function in a class with a user-declared copy assignment operator or destructor is deprecated.
• Copy assignment operator: Same runtime behavior as C++98: memberwise copy assignment of non-static data members. Generated only if the class lacks a user-declared copy assignment operator. Deleted if the class declares a move operation. Generation of this function in a class with a user-declared copy constructor or destructor is deprecated.
• Move constructor and move assignment operator: Each performs memberwise moving of non-static data members. Generated only if the class contains no user- declared copy operations, move operations, or destructor.
Note that there's nothing in the rules about the existence of a member function template preventing compilers from generating the special member functions. That means that if Widget
looks like this,
class Widget {
…
template // construct Widget
Widget(const T& rhs); // from anything
template // assign Widget
Widget& operator=(const T& rhs);// from anything
};
compilers will still generate copy and move operations for Widget
(assuming the usual conditions governing their generation are fulfilled), even though these templates could be instantiated to produce the signatures for the copy constructor and copy assignment operator. (That would be the case when T
is Widget
.) In all likelihood, this will strike you as an edge case barely worth acknowledging, but there's a reason I'm mentioning it. Item 26demonstrates that it can have important consequences.
Things to Remember
• The special member functions are those compilers may generate on their own: default constructor, destructor, copy operations, and move operations.
• Move operations are generated only for classes lacking explicitly declared move operations, copy operations, and a destructor.
• The copy constructor is generated only for classes lacking an explicitly declared copy constructor, and it's deleted if a move operation is declared. The copy assignment operator is generated only for classes lacking an explicitly declared copy assignment operator, and it's deleted if a move operation is declared. Generation of the copy operations in classes with an explicitly declared destructor is deprecated.
• Member function templates never suppress generation of special member functions.
Poets and songwriters have a thing about love. And sometimes about counting. Occasionally both. Inspired by the rather different takes on love and counting by Elizabeth Barrett Browning (“How do I love thee? Let me count the ways.”) and Paul Simon (“There must be 50 ways to leave your lover.”), we might try to enumerate the reasons why a raw pointer is hard to love:
1. Its declaration doesn't indicate whether it points to a single object or to an array.
2. Its declaration reveals nothing about whether you should destroy what it points to when you're done using it, i.e., if the pointer owns the thing it points to.
3. If you determine that you should destroy what the pointer points to, there's no way to tell how. Should you use delete
, or is there a different destruction mechanism (e.g., a dedicated destruction function the pointer should be passed to)?
4. If you manage to find out that delete
is the way to go, Reason 1 means it may not be possible to know whether to use the single-object form (“ delete
”) or the array form (“ delete []
”). If you use the wrong form, results are undefined.
5. Assuming you ascertain that the pointer owns what it points to and you discover how to destroy it, it's difficult to ensure that you perform the destruction exactly once along every path in your code (including those due to exceptions). Missing a path leads to resource leaks, and doing the destruction more than once leads to undefined behavior.
6. There's typically no way to tell if the pointer dangles, i.e., points to memory that no longer holds the object the pointer is supposed to point to. Dangling pointers arise when objects are destroyed while pointers still point to them.
Raw pointers are powerful tools, to be sure, but decades of experience have demonstrated that with only the slightest lapse in concentration or discipline, these tools can turn on their ostensible masters.
Smart pointers are one way to address these issues. Smart pointers are wrappers around raw pointers that act much like the raw pointers they wrap, but that avoid many of their pitfalls. You should therefore prefer smart pointers to raw pointers. Smart pointers can do virtually everything raw pointers can, but with far fewer opportunities for error.
There are four smart pointers in C++11: std::auto_ptr
, std::unique_ptr
, std::shared_ptr
, and std::weak_ptr
. All are designed to help manage the lifetimes of dynamically allocated objects, i.e., to avoid resource leaks by ensuring that such objects are destroyed in the appropriate manner at the appropriate time (including in the event of exceptions).
std::auto_ptr
is a deprecated leftover from C++98. It was an attempt to standardize what later became C++11's std::unique_ptr
. Doing the job right required move semantics, but C++98 didn't have them. As a workaround, std::auto_ptr
co-opted its copy operations for moves. This led to surprising code (copying a std::auto_ptr
sets it to null!) and frustrating usage restrictions (e.g., it's not possible to store std::auto_ptr
s in containers).
std::unique_ptr
does everything std::auto_ptr
does, plus more. It does it as efficiently, and it does it without warping what it means to copy an object. It's better than std::auto_ptr
in every way. The only legitimate use case for std::auto_ptr
is a need to compile code with C++98 compilers. Unless you have that constraint, you should replace std::auto_ptr
with std::unique_ptr
and never look back.
The smart pointer APIs are remarkably varied. About the only functionality common to all is default construction. Because comprehensive references for these APIs are widely available, I'll focus my discussions on information that's often missing from API overviews, e.g., noteworthy use cases, runtime cost analyses, etc. Mastering such information can be the difference between merely using these smart pointers and using them effectively .
Item 18: Use std::unique_ptr
for exclusive-ownership resource management.
When you reach for a smart pointer, std::unique_ptr
should generally be the one closest at hand. It's reasonable to assume that, by default, std::unique_ptr
s are the same size as raw pointers, and for most operations (including dereferencing), they execute exactly the same instructions. This means you can use them even in situations where memory and cycles are tight. If a raw pointer is small enough and fast enough for you, a std::unique_ptr
almost certainly is, too.
Читать дальше