Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Instantiating Code Patterns


JUN96: Instantiating Code Patterns

Patterns applied to software development

Fred is principal of Advantage Software Technologies and can be contacted at [email protected].


Although we often use patterns, we sometimes do so unconsciously--not by design. Whenever we follow a pattern, we are applying experienced-based learning. Whenever we need to accomplish something that we've seen others do, we tend to mimic the strategies and actions that made that thing successful in the past.

Experienced software developers use patterns heavily in the design-level aspects of their work. Design patterns make us more productive, more likely to achieve success, and generally more valuable as project participants.

We might think that less-experienced engineers are disadvantaged in this respect, but rather than accepting the risks that lack of experience brings with it, effective organizations take an early and active role in sharing important domain knowledge with them. They gather the collective design wisdom of the group, and document those best practices in a descriptive language (manifest vocabulary) of what the important design patterns are, and when and how to apply them. A good example of this can be found in Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma et al. (Addison-Wesley, 1995). In my experience, developers of all ranks drink up such design patterns and quickly begin to apply them effectively.

With respect to patterns of code, we can provide much more direct help and support than simply documenting best practices. We can build the use of those code patterns into the tools we use in the software-development process itself. SNIP is a commercially available tool I designed for that purpose. In this article, I'll discuss the use of patterns in the coding aspect of software development, and describe how to use SNIP to define and instantiate code patterns.

Design Patterns and Code Patterns

Design patterns are logical in nature. They embody approaches and strategies that are relevant to a number of possible implementations. A design pattern is something you can visualize independent of a particular programming language. For example, given that you need to manage the dynamic allocation and deallocation of objects, you can specify a logical strategy for minimizing memory utilization by keeping reference counts on objects and copying those objects only when an operation is performed to modify them. For a detailed C++ example of this strategy see "Item 29" within More Effective C++: 35 New Ways to Improve Your Programs and Designs, by Scott Meyers (Addison-Wesley, 1996). Having defined the strategy and given it a name, you can treat it as a design idiom. During design you can say, "XYZ will be a reference-counted object," and experienced programmers will understand what that implies.

In contrast, code patterns are physical in nature. They focus on how a particular structure or sequence of action is accomplished using the specific mechanisms of a programming language. With C++, we've been trained to create code-level abstractions to support certain design idioms (for example, using collection classes and templates), but this practice has limitations. Classes and templates are not adequate mechanisms to implement many of the code patterns that are interesting to us. Many important code patterns either describe how a class should deal with its parts (a parts-based pattern), or describe how a number of cooperating classes are created to implement a single design idea (a multiclass pattern). Developers currently use parts-based and multiclass patterns, but they do so via hand coding. You either know the pattern by heart, or you copy a code sample that already follows the pattern, and modify it to work in the specific case.

Using Code Patterns

Most production software environments operate under time and quality pressures. As a result, code patterns that should be staples are all-too-seldom used. This is not because the patterns are unknown. It is not because developers do not recognize cases in which they would be good strategies to apply. It is because hand-crafting patterns adds just enough time and risk to a schedule to cause them to be shunned. For example, a correct implementation of a reference-counted object in C++ requires careful attention to the use of a copy-on-write mechanism that each non-const function plays into. This adds unwelcome detail and complexity to a handcoding-based process.

To make use of nontrivial code patterns, we just need an easy way to characterize objects during design and then have those characteristics influence how code is produced.

Using SNIP for Pattern Instantiation

SNIP is a tool for instantiating patterns of code that can be derived from object models. First, it allows you to define code patterns that are important, based on your own local standards and classifications of object characteristics. SNIP then provides the capability to instantiate those code patterns for any set of specific objects. Given a properly thought-out set of rules for creating code for objects and their parts, based on specific characteristics, you can realize gains in standardization, quality, and time-to-implementation.

SNIP enables code patterns that use an object model as an instantiation contex to be defined; see Figure 1. It applies the rules called out in an executable template to the objects and their parts, and produces code files based on those rules.

To use SNIP, you first need a well-defined implementation strategy. You need to call out the characteristics of the objects (and their parts) that participate in that strategy, and decide how code should be created for each of them. Given that strategy, you create a set of code-creation rules, placing them in a SNIP template file. A number of template files that serve as good starting points for this come with the tool. For example, a template for handling frequent C++ class constructs that can be easily extended to include special rules is provided.

You can develop the template files iteratively using SNIP's user interface. Once you are satisfied with the code pattern that is created by the template, you can add the command form of SNIP into makefiles for use in non-interactive builds.

As an example of how SNIP is used, I'll use the common C++ programming task of properly managing dynamically allocated objects. Whenever you have a contained pointer, it is important for the code in the class containing the pointer to manage the dynamically allocated object correctly. (Although you might otherwise create a special container class or smart-pointer template, I'll use this example because it is familiar and brief.)

First, I'll define what it means to manage an object via a contained pointer. (Feel free to disagree with the definition--it just means your template would be different than the one I present.) I define the code-pattern requirements as follows:

  • All pointers to objects, both owned and shared, are initially set to NULL.
  • A set operation keeps the pointer passed into it in both the owned and shared pointer cases.
  • A clear operation deletes the object if it is owned, otherwise the pointer is set to NULL.
  • The destructor deletes the object if it is owned.
  • A copy constructor or assignment operation calls makeCopy() on an owned object and uses the returned pointer. Pointers are simply copied in shared object cases.
Listing One shows how these rules are expressed in the SNIP-template file format. Before inspecting the listing, however, note that this template is pared down for illustration, so it won't be hard to find a case it doesn't handle (the production template that this example was taken from is much more sophisticated). Secondly, there are only a few constructs used in SNIP templates. Once you know them, readability isn't an issue.

SNIP's template statements draw information from an object model and generate code based on that information. Before you can make sense of template statements, you need to see what they are referring to. Listing Two is a simple object model I'll use as input to SNIP in this example. It has just one contained pointer of each type in it--one to an object that is owned (PetOwner::Pet), and another to an object that is shared (PetOwner::Vet). When you apply the template file in Listing One to this object model, your rules will be used to generate the code in Listings Three (the resulting header file) and Four (the resulting body file).

Reading Template Statements

SNIP template files contain a mainline of executable statements and any number of modules that contain executable statements. Executable statements come in three forms: simple statements, blocks, and iterators. Simple statements have the form left-hand-side ':' right-hand-side. To the left of the colon is a selection expression. If the expression evaluates to True, the right side is expanded, and the resulting text is emitted into the active output file.

The module emit_class_decls in Listing One contains the segment of statements in Example 1. The first five statements have no variables on the left, which means they are selected in all cases, so each of their right sides are expanded and emitted into the active output file (variables being substituted where indicated). A backslash on the end of a statement continues the line--no line feed is emitted when the right side's text is emitted.

The next line has a single variable on the left, called <obj.has_parent>, which takes on the value true if the object has at least one parent object identified in the input model. When it is True, the statement is selected. It emits a colon and space, and continues the line.

The next statement is an iterator, prefaced by .each_parent. It iterates over each parent identified for the current object in the model. The body of the iterator continues until its corresponding .end is reached. Within this parent iterator, each parent class name is emitted, followed by a comma, except with the last one. The variable <xxx.is_nth> is used to indicate the last member of an iterated list. The bang character (!) inverts the sense of the Boolean variable.

This segment of the template emits code that properly creates the preamble of a class' declaration and takes into account cases where the class is a subclass of one or more parent classes.

Code for Handling Contained Pointers

Listing Two contains the template module emit_class_copy_ctor that will emit the copy constructor for each object in the model. The pattern that the copy constructor follows demonstrates the need to treat contained pointers to owned objects differently from those that are shared.

The module emit_class_copy_ctor starts by emitting the copy constructor's signature and initialization list, including calls to copy constructors higher up the inheritance paths, if there are any. After emitting the opening brace of the constructor's body, the attributes that have been defined for the object are iterated, and three possibilities are used as selection criteria for producing code statements. The attribute may be simple, such as Name. In this case, the template assumes that the assignment operator is defined for the attribute type, and adds a statement to copy its associated value. Second, the attribute may be a pointer that is shared. In that case, the pointer value is copied. In the last case, you have a pointer that is owned. A call is issued to make a copy of the object in that case, and the pointer of the newly allocated object is retained. This module is also responsible for emitting the body of the makeCopy function, which appears at its end. When this module is applied to PetOwner, you get the code in the class PetOwner shown in Listing Four.

The destructor pattern (see the module emit_class_destructor in Listing Two) iterates similarly over the object's attributes. This time the iteration is done to find only the owned pointers. These are the only members that are interesting to the destructor (which is responsible for deleting them). The destructor for PetOwner has only one line, which deletes the Pet object, its sole owned pointer. Example 2 (excerpted from Listing Four) is the code created for PetOwner's destructor.

Conclusion

SNIP allows control of code generation, and is flexible enough that you can define code patterns that relate to how objects fit into your local frameworks. Where a consistent strategy and pattern of producing code can be identified, quality and productivity can be increased proportional to the amount of code that follows that strategy. The ratio of lines of code to lines of input model is typically 20:1. This is a great productivity advantage. Admittedly, SNIP does not provide a revolutionary new way to produce code. Instead, it offers the opportunity to automate what you are already doing to follow code patterns by hand.

For More Information

Advantage Software Technologies

260 Abbott Run Valley Rd.

Cumberland, RI 02864

401-334-4807

[email protected].

SNIP In Action

Mo Bjornestad

< cite>Mo, vice president of marketing for Mark V Systems, can be contacted at mob@ markv.com or http://www.markv.com.

CASE tools have traditionally focused on providing a means of modeling analysis and design problems using the notations of prescribed methodologies, such as those offered by Booch and Rumbaugh. As such, CASE tools have too often been used as a special-purpose document-capture facility. This is an unenlightened use. Modern CASE tools are now used more appropriately as rapid application development (RAD) tools that generate applications in whole or in part. That is what Mark V's ObjectMaker is designed to do.

ObjectMaker supplies a meta-CASE capability, which is another way to say that it offers a highly extensible system that provides a breadth of customization. ObjectMaker users simply build graphical models of their requirements and design. The graphical diagrams are transformed into a unified semantic model. The semantic model is then transformed to SNIP's macro language, which is sent to SNIP for template expansion into the targeted programming language(s). The template can be edited for form and content to conform to internal standards. (Templates are currently available for Ada 83, Ada 95, C, C++, and Smalltalk, with IDL and Java templates under development.) In short, users can define their own variants of traditional methods, or add completely new methods. They can then use the information captured in the repository in forward engineering into code. That's where SNIP comes in.

SNIP allows ObjectMaker to remain customizable. ObjectMaker's front end is integrated with SNIP's capability to instantiate patterns of code from object models. SNIP integration is accomplished by using Mark V's meta rule language to traverse and transform the ObjectMaker semantic model contents into the SNIP macro language. The macro is handed off to a macro expander, which populates the templates, then exports the file as fully expanded code. We feed the object model information into SNIP from our repository, and let it generate code files according to user-modifiable code patterns delivered with ObjectMaker. SNIP is the right tool for this because it is largely model and language independent.

Using SNIP has exposed many opportunities and possibilities for patterns to be applied in creating code from design-level information. As people's ideas about using code patterns become more focused, we see a renewed and growing interest in CASE as a front end for driving pattern-based approaches to creating software. SNIP provides a timely capability in this area.

Figure 1: SNIP's operating model.

Example 1: Reading template statements.

               ://
               ://  Class <obj.name> -------------
               ://
               :
               :class <obj.name> \
<obj.has_parent>     :: \
.each_parent
               :public <parent.name>\
<!parent.is_nth>     :, \
<parent.is_nth>     :
.end
               :{

Example 2: Code created for PetOwner's destructor.

PetOwner::~PetOwner()
{
   delete m_Pet ;
}

Listing One

object PetOwner  
    Name            :       string ;
    Pet             :       ptr_to  Pet [is_owner] ;
    Vet             :       ptr_to  Veterinarian [is_shared] ;
end ;
object Veterinarian  
    Name            :       string ;
    StreetAddress   :       string ;
    Town            :       string ;
    Phone           :       string ;
end ;
object Pet 
    Name            :       string ;
    KindOfPet       :       string ;
end ;
foreign string [use_ref] ;

Listing Two

#   Create the header file ...
#
                :$$NEWFILE <dsm.name>.hxx
                :
                :#ifndef <dsm.name>_H
                :#define <dsm.name>_H
                :
                :// Forward references for each class
                :
.each_obj
                :class  <obj.name> ;
.end
                :
                :$$EXECMODULE emit_class_decls
                :
                :#endif
                :
#   Create the body file ...
#
                :$$NEWFILE <dsm.name>.cxx
                :
                :#include "<dsm.name>.hxx"
                :
                :$$EXECMODULE emit_class_bodies
                :
###################################################################
#   Module to emit class declarations
#
.module emit_class_decls
.each_obj
                        :
                        ://
                        ://  Class <obj.name> --------------------------------
                        ://
                        :
                        :class <obj.name> \
<obj.has_parent>        :: \
.each_parent
                        :public <parent.name>\
<!parent.is_nth>        :, \
<parent.is_nth>         : 
.end
                        :{
                        :$$EXECMODULE emit_member_variables
                        :
                        :   public:
                        :
                        :       <obj.name>();
                        :       <obj.name>(const <obj.name>& obj);
                        :       <obj.name> *makeCopy() const ;
                        :       virtual ~<obj.name>();
                        :
                        :       <obj.name> &operator=(const <obj.name> &rhs) ;
                        :
                        :$$EXECMODULE emit_attr_access_ftn_decls
                        :
                        :} ;
.end
.end
###################################################################
#   Modules to emit class member variable declarations
#
.module emit_member_variables
.each_attr
<attr.simple>       :       <attr.kind>       <30>  m_<attr.name> ;
<attr.ptr_to>       :       <attr.kind>       <30> *m_<attr.name> ;
.end
.end
###################################################################
#   Module to emit attribute access function declarations
#
.module emit_attr_access_ftn_decls
.each_attr
                    :$$EXECMODULE emit_get_decl
                    :$$EXECMODULE emit_set_decl
                    :
.end
.end
###################################################################
#   Module to emit get functions for an attribute
#
.module emit_get_decl
<attr.simple><!attr.use_ref> : <attr.kind>   <35>Get<attr.name> () const ;
<attr.simple><attr.use_ref>  : const <attr.kind> & <35>Get<attr.name> () const;
<attr.ptr_to>                : const <attr.kind> * <35>Get<attr.name> () const;
.end
###################################################################
#   Module to emit set functions for an attribute
#
.module emit_set_decl
<attr.simple><!attr.use_ref> : void <35>Set<attr.name>(const<attr.kind> val);
<attr.simple><attr.use_ref>  : void <35>Set<attr.name>(const<attr.kind> & val);
<attr.ptr_to>                : void <35>Set<attr.name>(<attr.kind> *val) ; 
<attr.ptr_to>                : void <35>Clear<attr.name> () ; 
.end
###################################################################
#   Module to emit the bodies of member functions for each class
#
.module emit_class_bodies
.each_obj
            :
            ://////////////////////////////////////////////////////////
            ://     class <obj.name>
            :$$EXECMODULE emit_class_default_ctor
            :$$EXECMODULE emit_class_copy_ctor
            :$$EXECMODULE emit_class_destructor
            :$$EXECMODULE emit_assignment_op_body
            :$$EXECMODULE emit_get_and_set_ftn_bodies
.end
.end
###################################################################
#   Module to create a class' default constructor
#
.module emit_class_default_ctor
                                 :
                                 :<obj.name>::<obj.name>()
                                 :{
.each_attr
<attr.ptr_to><!attr.has_init>    :   m_<attr.name> = NULL;
<attr.has_init>                  :   m_<attr.name> = <attr.init_val> ;
.end
                                 :}
.end
###################################################################
#   Module to create a class' copy ctor and 'makeCopy' function
#
.module emit_class_copy_ctor
                                 :
                                 :<obj.name>::<obj.name>(const <obj.name>& obj)
<obj.has_parent>                 :   : \
.each_parent
                                 :<parent.name>(obj)\
<!parent.is_nth>                 :,
<!parent.is_nth>                 :     \
<parent.is_nth>                  :
.end
                                 :{
.each_attr
<attr.simple>                  : m_<attr.name> = obj.m_<attr.name>;
<attr.ptr_to><attr.is_shared>  : m_<attr.name> = obj.m_<attr.name>;
<attr.ptr_to><attr.is_owner>   : m_<attr.name> = obj.m_<attr.name>->makeCopy();
.end
                                 :}
                                 :
                                 :<obj.name> *<obj.name>::makeCopy() const 
                                 :{
                                 :    return new <obj.name>(*this);
                                 :}
.end
###################################################################
#   Module to create a class' destructor
#
.module emit_class_destructor
                                 :
                                 :<obj.name>::~<obj.name>()
                                 :{
.each_attr
<attr.ptr_to><attr.is_owner>     :   delete m_<attr.name> ;
.end
                                 :}
.end
###################################################################
#   Module to create a class' assignment operator
#
.module emit_assignment_op_body
                                :
                                :<obj.name> &
                                :<obj.name>::operator=(const <obj.name> &rhs)
                                :{
                                :   if (this == &rhs) return *this ;
                                :       // No changes if assignment to self 
                                :
.each_parent
                                :   ((<parent.name> &) (*this)) = rhs ;  
                                :       // Assign base class members of <parent.name>
.end
.each_attr
<attr.simple>                  : m_<attr.name> = rhs.m_<attr.name>;
<attr.ptr_to><attr.is_shared>  : m_<attr.name> = rhs.m_<attr.name>;
<attr.ptr_to><attr.is_owner>   : m_<attr.name> = rhs.m_<attr.name>->makeCopy();
.end
                                :
                                :   return *this ;
                                :}
.end
###################################################################
# Module to create class member functions that access single valued member vars
#
.module emit_get_and_set_ftn_bodies
.each_attr
<attr.getset>   :$$EXECMODULE emit_get_ftn_body
<attr.getset>   :$$EXECMODULE emit_set_and_clear_ftn_bodies
.end
.end
###################################################################
#   Module to create a 'Get' function body for accessing an attribute
#
.module emit_get_ftn_body
<attr.simple><!attr.use_ref>    :<attr.kind> Get<attr.name> () const
<attr.simple><attr.use_ref>     :const <attr.kind> & Get<attr.name> () const 
<attr.ptr_to>                   :const <attr.kind> * Get<attr.name> () const 
                                :{
                                :    return m_<attr.name> ;
                                :}
.end
###################################################################
#   Module to create a 'Set' and 'Clear' function bodies
#
.module emit_set_and_clear_ftn_bodies
<attr.simple><!attr.use_ref>    :void Set<attr.name> (const <attr.kind> val)
<attr.simple><attr.use_ref>     :void Set<attr.name> (const <attr.kind> & val)
<attr.ptr_to>                   :void Set<attr.name> (<attr.kind> *val)
                                :{
<attr.simple>                   :  <attr.name> = val ;
<attr.ptr_to><attr.is_owner>    :  if (<attr.name> != NULL) delete <attr.name>;
<attr.ptr_to>                   :  <attr.name> = val ;
                                :}
<attr.ptr_to>                   :
<attr.ptr_to>                   :
<attr.ptr_to>                   :void Clear<attr.name> ()
<attr.ptr_to>                   :{
<attr.ptr_to><attr.is_owner>    : if (<attr.name> != NULL) delete <attr.name> ;
<attr.ptr_to>                   : <attr.name> = NULL ;
<attr.ptr_to>                   :}
.end
.end

Listing Three

#ifndef pets_H
#define pets_H
// Forward references for each class
class  PetOwner ;
class  Veterinarian ;
class  Pet ;
//  Class PetOwner --------------------------------
class PetOwner {
       string                  m_Name ;
       Pet                    *m_Pet ;
       Veterinarian           *m_Vet ;
   public:
       PetOwner();
       PetOwner(const PetOwner& obj);
       PetOwner *makeCopy() const ;
       virtual ~PetOwner();
       PetOwner &operator=(const PetOwner &rhs) ;
       const string &             GetName () const ;
       void                       SetName (const string & val) ;
       const Pet *                GetPet () const ;
       void                       SetPet (Pet *val) ; 
       void                       ClearPet () ; 
       const Veterinarian *       GetVet () const ;
       void                       SetVet (Veterinarian *val) ; 
       void                       ClearVet () ; 
} ;
//  Class Veterinarian --------------------------------
class Veterinarian {
       string                  m_Name ;
       string                  m_StreetAddress ;
       string                  m_Town ;
       string                  m_Phone ;
   public:
       Veterinarian();
       Veterinarian(const Veterinarian& obj);
       Veterinarian *makeCopy() const ;
       virtual ~Veterinarian();
       Veterinarian &operator=(const Veterinarian &rhs) ;
       const string &             GetName () const ;
       void                       SetName (const string & val) ;
       const string &             GetStreetAddress () const ;
       void                       SetStreetAddress (const string & val) ;
       const string &             GetTown () const ;
       void                       SetTown (const string & val) ;
       const string &             GetPhone () const ;
       void                       SetPhone (const string & val) ;
} ;
//  Class Pet --------------------------------
class Pet {
       string                  m_Name ;
       string                  m_KindOfPet ;
   public:
       Pet();
       Pet(const Pet& obj);
       Pet *makeCopy() const ;
       virtual ~Pet();
       Pet &operator=(const Pet &rhs) ;
       const string &             GetName () const ;
       void                       SetName (const string & val) ;
       const string &             GetKindOfPet () const ;
       void                       SetKindOfPet (const string & val) ;
} ;
#endif

Listing Four

#include "pets.hxx"
//////////////////////////////////////////////////////////
//     class PetOwner
PetOwner::PetOwner()
{
   m_Pet = NULL;
   m_Vet = NULL;
}
PetOwner::PetOwner(const PetOwner& obj)
{
   m_Name = obj.m_Name;
   m_Pet = obj.m_Pet->makeCopy();
   m_Vet = obj.m_Vet;
}
PetOwner *PetOwner::makeCopy() const 
{
    return new PetOwner(*this);
}
PetOwner::~PetOwner()
{
   delete m_Pet ;
}
PetOwner &
PetOwner::operator=(const PetOwner &rhs)
{
   if (this == &rhs) return *this ;
       // No changes if assignment to self 
   m_Name = rhs.m_Name;
   m_Pet = rhs.m_Pet->makeCopy();
   m_Vet = rhs.m_Vet;
   return *this ;
}
//////////////////////////////////////////////////////////
//     class Veterinarian
Veterinarian::Veterinarian()
{
}
Veterinarian::Veterinarian(const Veterinarian& obj)
{
   m_Name = obj.m_Name;
   m_StreetAddress = obj.m_StreetAddress;
   m_Town = obj.m_Town;
   m_Phone = obj.m_Phone;
}
Veterinarian *Veterinarian::makeCopy() const 
{
    return new Veterinarian(*this);
}
Veterinarian::~Veterinarian()
{
}
Veterinarian &
Veterinarian::operator=(const Veterinarian &rhs)
{
   if (this == &rhs) return *this ;
       // No changes if assignment to self 
   m_Name = rhs.m_Name;
   m_StreetAddress = rhs.m_StreetAddress;
   m_Town = rhs.m_Town;
   m_Phone = rhs.m_Phone;
   return *this ;
}
//////////////////////////////////////////////////////////
//     class Pet
Pet::Pet()
{
}
Pet::Pet(const Pet& obj)
{
   m_Name = obj.m_Name;
   m_KindOfPet = obj.m_KindOfPet;
}
Pet *Pet::makeCopy() const 
{
    return new Pet(*this);
}
Pet::~Pet()
{
}
Pet &
Pet::operator=(const Pet &rhs)
{
   if (this == &rhs) return *this ;
       // No changes if assignment to self 
   m_Name = rhs.m_Name;
   m_KindOfPet = rhs.m_KindOfPet;
   return *this ;
}


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.