std::shared_ptr spw(new Widget, cusDel);
processWidget(spw, computePriority()); // correct, but not
// optimal; see below
This works, because a std::shared_ptrassumes ownership of the raw pointer passed to its constructor, even if that constructor yields an exception. In this example, if spw's constructor throws an exception (e.g., due to an inability to dynamically allocate memory for a control block), it's still guaranteed that cusDelwill be invoked on the pointer resulting from “ new Widget”.
The minor performance hitch is that in the exception-unsafe call, we're passing an rvalue to processWidget,
processWidget(
std::shared_ptr(new Widget, cusDel), // arg is rvalue
computePriority()
);
but in the exception-safe call, we're passing an lvalue:
processWidget( spw, computePriority()); // arg is lvalue
Because processWidget's std::shared_ptrparameter is passed by value, construction from an rvalue entails only a move, while construction from an lvalue requires a copy. For std::shared_ptr, the difference can be significant, because copying a std::shared_ptrrequires an atomic increment of its reference count, while moving a std::shared_ptrrequires no reference count manipulation at all. For the exception-safe code to achieve the level of performance of the exception-unsafe code, we need to apply std::moveto spwto turn it into an rvalue (see Item 23):
processWidget( std::move(spw ), // both efficient and
computePriority()); // exception safe
That's interesting and worth knowing, but it's also typically irrelevant, because you'll rarely have a reason not to use a makefunction. And unless you have a compelling reason for doing otherwise, using a makefunction is what you should do.
Things to Remember
• Compared to direct use of new, make functions eliminate source code duplication, improve exception safety, and, for std::make_sharedand std::allocate_shared, generate code that's smaller and faster.
• Situations where use of make functions is inappropriate include the need to specify custom deleters and a desire to pass braced initializers.
• For std::shared_ptrs, additional situations where make functions may be ill-advised include (1) classes with custom memory management and (2) systems with memory concerns, very large objects, and std::weak_ptrs that outlive the corresponding std::shared_ptrs.
Item 22: When using the Pimpl Idiom, define special member functions in the implementation file.
If you've ever had to combat excessive build times, you're familiar with the Pimpl (“pointer to implementation”) Idiom . That's the technique whereby you replace the data members of a class with a pointer to an implementation class (or struct), put the data members that used to be in the primary class into the implementation class, and access those data members indirectly through the pointer. For example, suppose Widgetlooks like this:
class Widget { // in header "widget.h"
public:
Widget();
…
private:
std::string name;
std::vector data;
Gadget g1, g2, g3; // Gadget is some user-
}; // defined type
Because Widget's data members are of types std::string, std::vector, and Gadget, headers for those types must be present for Widgetto compile, and that means that Widgetclients must #include , , and gadget.h. Those headers increase the compilation time for Widgetclients, plus they make those clients dependent on the contents of the headers. If a header's content changes, Widgetclients must recompile. The standard headers and don't change very often, but it could be that gadget.his subject to frequent revision.
Applying the Pimpl Idiom in C++98 could have Widgetreplace its data members with a raw pointer to a struct that has been declared, but not defined:
class Widget { // still in header "widget.h"
public:
Widget();
~Widget(); // dtor is needed-see below
…
private:
struct Impl; // declare implementation struct
Impl *pImpl; // and pointer to it
};
Because Widgetno longer mentions the types std::string, std::vector, and Gadget, Widgetclients no longer need to #includethe headers for these types. That speeds compilation, and it also means that if something in these headers changes, Widgetclients are unaffected.
A type that has been declared, but not defined, is known as an incomplete type . Widget::Implis such a type. There are very few things you can do with an incomplete type, but declaring a pointer to it is one of them. The Pimpl Idiom takes advantage of that.
Part 1 of the Pimpl Idiom is the declaration of a data member that's a pointer to an incomplete type. Part 2 is the dynamic allocation and deallocation of the object that holds the data members that used to be in the original class. The allocation and deallocation code goes in the implementation file, e.g., for Widget, in widget.cpp:
#include "widget.h" // in impl.file "widget.cpp"
#include "gadget.h"
#include
#include
struct Widget::Impl { // definition of Widget::Impl
std::string name; // with data members formerly
std::vector data; // in Widget
Gadget g1, g2, g3;
};
Widget::Widget() // allocate data members for
: pImpl( new Impl) // this Widget object
{}
Widget::~Widget() // destroy data members for
{ delete pImpl;} // this object
Here I'm showing #includedirectives to make clear that the overall dependencies on the headers for std::string, std::vector, and Gadgetcontinue to exist. However, these dependencies have been moved from widget.h(which is visible to and used by Widgetclients) to widget.cpp(which is visible to and used only by the Widgetimplementer). I've also highlighted the code that dynamically allocates and deallocates the Implobject. The need to deallocate this object when a Widgetis destroyed is what necessitates the Widgetdestructor.
But I've shown you C++98 code, and that reeks of a bygone millennium. It uses raw pointers and raw newand raw deleteand it's all just so…raw. This chapter is built on the idea that smart pointers are preferable to raw pointers, and if what we want is to dynamically allocate a Widget::Implobject inside the Widgetconstructor and have it destroyed at the same time the Widgetis, std::unique_ptr(see Item 18) is precisely the tool we need. Replacing the raw pImplpointer with a std::unique_ptryields this code for the header file,
Читать дальше