Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 21.  Veneers


21.3. Rubbing Up to Concepts

When we looked at conversion shims, we saw that one answer to making CString work with our string tokenizer (see section 20.5) was to specialize the traits template and provide the necessary types. While this solution worked perfectly well, we've run into one of the problems with the traits technique. It's great if all we want to use the CString for is with one tokenizer type. But consider now that we want to use Acme Web Software's template, html_link_parser, which parses HTML documents and places all the hyperlinks in a container of strings. As is the case with the string tokenizer, acmeweb::html_link_parser is parameterized by the string type and uses an html_string_traits template to deduce string characteristics.

The default definition of acmeweb::html_string_traits reasonably requires that its parameterizing string type conforms to the standard library's String model (C++-98: 21.3) and assumes most cases will use std::string. However, this is not always possible or optimal. So to use CString with html_link_parser, what are our alternatives?

We could simply go the hard-coded route, and specialize html_string_traits for CString. But we've already done a specialization for the string tokenizer. Good sense, programming lore, and the instinctive apathy of all good software engineers tells us that this is wrong. Good software engineering practice involves doing things twice, once to learn, once to generalize. If we come across a third processing class from Job Creation Incorporated, do we define a third specialization? A one-time only alternative to this is to give CString a serious promotion into the big league, and let it parade around in team colors. Sounds like a problem for a veneer.

It's really simple, and it looks like this.

Listing 21.7.


class CString_veneer


  : protected CString


{


public:


  typedef TCHAR       value_type;


  typedef TCHAR       *iterator;


  typedef TCHAR const *const_iterator;


  typedef TCHAR       *pointer;


  typedef TCHAR const *const_pointer;


  typedef size_t      size_type;


// Construction


public:


  explicit CString_veneer(const_pointer s);


  explicit CString_veneer(const_pointer from, const_pointer to);


// Iteration


public:


  const_iterator  begin() const;


  const_iterator  end() const;


// Access


public:


  const_pointer c_str() const;


};





inline TCHAR const *c_str_ptr(CString_veneer const &s) // Shim


{


  return s.c_str();


}



You might be saying "But it's missing some methods: I can't see some constructor overloads, and operators, or the find_kitchen_sink_last_not_of_where_next_to_larder() method." Well that's perfectly fine. What we have here is a beautifully contained playpen in which to mess around. We know the starting point: the embarrassingly naked (from the STL point of view of, anyway) CString. We know the end point: the types, variables (actually variable, npos), and methods of the standard library's String model (C++-98: 21.3). We're not going to go outside that, unless we deliberately choose to be perverse, but that doesn't mean we have to complete it until we need to. How's that for facilitating lazy tendencies: you can complete it at your leisure, and there's no downside. (For those of you who defend the std::basic_string template against accusations that it has too many methods, I challenge you to create a veneer in the manner we are talking about, use it throughout all your work, populate it only as need dictates, and write to me when it matches the dictates of the String model. Note that by 2030 I expect to be living in a bubble on the lower slopes of Olympus Mons, so you'll have to send it care of Addison-Wesley Mars Division.)

You might also be saying "But you've derived from a class that doesn't have a virtual destructor, and everyone knows you shouldn't do that!" Well the classic advice when using inheritance is to "make sure base classes have virtual destructors" [Meye1998]. This advice is 100 percent correct when you are dealing with polymorphic types, and failure to do so will leave you an unhappy chappy. The rationale is that if your derived classes have a nontrivial destructor, and the virtual destructor mechanism is not employed to ensure correct destruction, resources will leak when they are deleted polymorphically (via a pointer to a base class).

Grown-ups know, however, that this advice is not germane when dealing with nonpolymorphic classes, although, to be fair, this knowledge is usually couched within the caveat that nonpolymorphic classes should not be used as bases. Since veneers are constrained to respect the polymorphic nature of their primary parameterizing types, they will be polymorphic (and, therefore, safe for destruction via base pointer) if their primary parameterizing type is, and will not be (and, therefore, not safe for destruction via base pointer) if their primary parameterizing type is not. Furthermore, users of a parameterization of your veneer have no business misusing its polymorphic (or lack of) nature. In plain English, if someone wraps your veneer around something that lacks a virtual destructor, it is their responsibility to ensure they follow the rules, and do not attempt to destroy it virtually. If they have not read your documentation, then give them a gentle reminder. If you didn't document your veneer (including a reference to the rules of veneers), then you'd better start baking that Humble Pie.


      Previous section   Next section