y recent C++ 10-Minute Solution titled “Using a Good Parasite Class to Design a Self-Clearing Memory Buffer” was greeted with skepticism, raised eyebrows, and a torrent of questions from readers who were puzzled by the notion of objects that are never destroyed?seemingly a violation of the C++ object model.
Readers have also wondered what other advantages a good parasite class might offer and whether they can declare additional member functions in such a class. In the following sections, I answer these and many other questions.
Good Parasite Q & A
Q. What Exactly Is a “Good Parasite” Class?
A.Unlike ordinary C++ classes, the good parasite has unique design requirements. Here’s the recipe for a good parasite class:
- You declare the destructor, assignment operator, copy constructor, and new operator private and delete them.
- You can instantiate a good parasite object only using placement new; all other conventional methods of instantiation, such as allocating the object on the stack, defining a static object, and so on, are disabled.
- A good parasite cannot have any data members, nor can it declare virtual member functions. This restriction guarantees that a good parasite object will have a practically zero-memory footprint.
- The constructor performs the operation that the good parasite should apply to the buffer on which it’s allocated. You can declare additional member functions as long as they comply with certain restrictions (explained below).
- A good parasite object lives on a dual-purpose memory buffer. In the example from the 10-Minute Solution, the raw buffer serves both as the substratum of the Zeroer object and as the SMS record itself. In other words, unlike conventional placement new usage, you’re not dealing with a raw memory buffer that was allocated for the sole purpose of hosting another object. Instead, the good parasite is closer to a union that has two members coexisting on the same memory address.
- Every time you want to invoke the operation that the good parasite represents, you construct a new object on the same memory address.
- Good parasites are never destroyed.
Q. Why Use the Good Parasite Idiom Over Other Techniques for Solving the Self-Recycling Buffer Problem?
A.You don’t really have to use the good parasite class for implementing a self-recycling buffer. Indeed, there are dozens of ways to implement a self-recycling buffer, some dating back to the early 1970s. Most of these designs rely on orthodox, well-tested, and non-controversial C and C++ concepts.
I used the self-recycling buffer problem as just a pretext for presenting a revolutionary and controversial feature of C++09. I believe that the good parasite can be useful in solving other programming problems, such as traversing the records of a database table, representing a media player’s playlist, or anywhere you need a fixed-sized memory buffer.
Q. Why Do You Consider std::vector a Bad Choice for Implementing a Self-Recycling Buffer?
A. Although you can get by with std::vector, it’s not an ideal choice for this specific task for three reasons:
- Allocation time. The self-recycling buffer’s size is fixed and known at compile-time. From a performance and an exception-safety standpoint, deferring the buffer allocation to runtime is pointless.
- Space. The std::vector template incurs considerable memory overhead (32 bytes on my compiler), which you can eliminate completely by using a raw memory buffer or the std::array template. Recall that the main application engine may launch thousands of threads, each with a unique buffer. Under these circumstances, you cannot justify the space overhead of std::vector. In some cases, it’s simply unacceptable.
- Ownership. The std::vector template owns the buffer, which means the buffer is unconditionally destroyed when the vector is destroyed. In most cases, this is well-intended?unless you want to decouple the raw memory buffer from the object that manipulates it, which is exactly what the good parasite does.
Q. How Can You Claim That vector::clear() Causes Reallocation When clear() Doesn’t Change the Capacity of Vector?
A. I stand corrected. Several members of the C++ standards committee who read the 10-Minute Solution have pointed out to me that the C++ standard guarantees?albeit in a very indirect way?that clear() doesn’t change the capacityof its vector; it changes only the vector’s size.
However, the committee members all agree that the current phrasing of the standard is underspecified in this respect. They propose that the standard should explicitly guarantee that no reallocations will occur as a result of calling clear(). In spite of that, I still find std::vectora poor choice for implementing a fixed-size buffer for the reasons listed above.
Q. Does C++09 Really Tolerate Types That Can Never Be Destroyed?
A. Most committee members reluctantly agree that the addition of deletedprivate destructors to C++09 effectively ushers in types that have no callable destructors. Here’s the chain of reasoning: C++09 supports deleted destructors. ?> The program can never call a deleted destructor. ?> A class that declares a private deleted destructor is practically indestructible, at least not in the conventional way.
Q. In Your Example, You Construct a New Object Over and Over Again on the Same Memory Address Without Ever Destroying the Previous Objects. Is This Valid C++?
A.I admit that the ice is thin here. C++ is rather strict about object lifetime issues, but the good parasite is an unusual beast. Since there’s nothing to destroy, you don’t really need to destroy the previous object before you construct a new one on top of it. More specifically, as every new object incurs zero space overhead, you’re not risking memory leaks. Because the good parasite doesn’t own any resources either, there’s no risk of resource leakage or race conditions. Recall that the usage of a good parasite is very restricted. For instance, you cannot create arrays of such objects nor can you store them in STL containers.
Q. Can a Good Parasite Class Declare Additional Member Functions?
A. Yes, it can. You may add more member functions to the good parasite as long as those members aren’t virtual. Of course, you cannot add any of the member functions and operators that are declared =delete.
As examples, you might add a member function called write_header() to the Zeroer class, which writes a header to the cleared buffer, or you might declare a member function that transforms the buffer to uppercase letters. Notice, however, that you cannot declare any data members in a good parasite class. In addition, such a class can have only non-virtual base classes that qualify for the empty base optimization.
Q. What’s the Good Parasite Good For in the Big Picture?
A. The good parasite class is an attempt to introduce a new type of function object to C++. Unlike traditional function objects, which have non-trivial initialization and destruction overhead and include data members in many cases, the good parasite is a pure function object: The very instantiation of such an object invokes the operation that the object represents. Thus, constructing a Zeroer object essentially clears the buffer. You achieve this by using the following design guidelines:
- Tuck the operation into the constructor of the good parasite.
- Avoid any data members.
- Eliminate the destructor.
This leads to an optimal design whereby:
1 object invocation(instantiation) == 1 function call
By contrast, when you’re using a traditional function object the ratio is:
1 function object invocation == 3 function calls
Where the three function calls consist of a constructor, operator(), and a destructor.
Q. If the Whole Purpose Is to Squeeze an Operation into a Constructor Call, Why Not Use a Simple Function?
A.I have no argument here. Calling a constructor amounts to calling a function, so there’s no significant difference (performance-wise) between a function and a good parasite class. Hence, theoretically, you can choose a simpler design that uses a plain old function instead.
However, notice that a traditional function would require additional arguments that indicate the buffer’s size and address. Forcing the programmer to pass these arguments explicitly in every function call is a security hazard. These arguments are eliminated when you use Zeroer<161>, because the size of the buffer is implicitly stored in the type of the Zeroer specialization, and the address of the buffer is cached in the thispointer.
Remember also that you can add more member functions to the good parasite, which makes this design more appealing and convenient than defining a procedural programming style list of unrelated functions.
Hopefully, this article has cleared up some of the confusion and controversy about the good parasite class and helped expand your C++ toolbox.