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

C/C++

Data Attribute Notation in C++


AUG94: Data Attribute Notation in C++

A coding style that emphasizes data abstraction

Reg is president of Charney & Day and a voting member of ANSI's X3J16 Committee on the C++ Language. He can be reached on CompuServe at 70272,3427.


Data Attribute Notation (DAN) is an object-oriented coding style that emphasizes data abstraction. DAN closely relates the abstract concepts defined in a project's analysis and design stages to the implementation stage. Thus, the differences across these three stages are minimized. The benefits of this include: better comprehension, higher productivity through clearer communications, and fewer errors as a result of fewer misunderstandings. DAN also provides better data abstraction, stronger type checking, greater clarity, and fewer errors than traditional programming styles.

Most C++ classes are composed of one or more components. As such, a class can be thought of as a collection of components. By implication, a derived class then is also a collection of components. Normally, components have a type and a unique name. These names usually reflect an attribute or property of a class. For example, a product has an id, a description, a cost price, and a list price. In Listing One , pid, desc, cost_price, and list_price are attributes of a product. If another component were added to the class, it would form another attribute for the abstract entity called a "product." For instance, a quantity attribute could be added to the class Product.

At this point, there are no surprises: A class is defined in terms of its components, or "attributes."

Attribute Classes

An attribute class is a class encapsulating a single logical entity, usually represented by one data member. Its interface is limited to a few operations that make logical sense for the logical entity in the class. Thus, an attribute class that defines product ids can ensure that only valid codes are created and only valid operations can be performed on them.

Constructors, including copy and conversion constructors, are used to create instances consistent with the meaning of the data. Conversion operators are also needed to allow instances of attribute classes to be used with non-user-defined data types. To read or write instances of attribute classes, input and output operations need to be defined. There may also be other member functions for attribute classes, depending on their requirements. Using the example of the product code, you can define an attribute class called Pid; see Listing Two .

Note that now, only certain defined operations are possible on a product id. Also, the actual implementation of a product code is hidden inside the class and its member functions. Thus, the Product class can be defined in terms of attribute classes (as in Listing Three), where the member names play a less important role in attribute classes than they would in normal classes that use normal coding practices. As such, the names in attribute classes tend to be shorter than normal. Since the elements of the class Product are private, Product users can treat the implementation of the class as a black box, allowing the implementation of any attribute class to change. Thus, the class Product can be considered a collection of attributes, and this leads to the question of how to manipulate the value of Product attributes.

Changing Attribute Class Values

An assignment operator is used to change values. However, it does not work well for assigning values to implicit members of the class Product. For example, in Listing Four , to what component is the value 7 assigned?

Defining assignment operators for individual attributes does not work well either, since it requires both that the user know the names of the components of the Product class and that the components be public, a clear violation of the principle of data hiding. Listing Five is a poor solution to this problem. Listing Six is the classic C++ method for changing attribute values. It requires that a user learn the names and prototypes for a number of member functions that can be used to get and set attribute values. An approach that uses data attributes and the black box, however, avoids the problems associated with both Listings Six and Seven.

DAN treats a given class as being defined by its attribute classes. Also, all attributes are unique in a given collection. In the Product example, both cost and list prices started out as type double, but ended up being represented by the unique attribute classes Cost and List.

There is a final piece of groundwork that needs to be laid; it is the analogy to the I/O streams classes. Most programmers use these classes without knowing how they are implemented. However, you can change the contents of a stream by using friend functions and overloading operator functions: for example, << for inserting data, and >> for extracting data. Thus, the stream classes are treated as black boxes that are modified using the insert and extract operators. Overloading the << and >> operators also allows streams to appear in expressions and be extensible.

DAN uses the insert and extract operators for the same reasons that I/O streams do. Other operators could have been used, and originally were, but the correspondence between I/O streams as black boxes and collection classes as black boxes was too good to ignore. Therefore, DAN uses the insert and extract operators to get and set data in collection classes. Single components can also be extracted from a collection by using the attribute classes as conversion operators. Listing Seven illustrates this.

Relationships

While attributes define the properties of a class, they also define relationships within a class. For example, all instances of the Product class must have a product id and a description. Attributes can define relationships between different classes. For example, you can define an invoice line-item class, called LineItem; see Listing Eight .

A relationship between an invoice line item and a product is formed by the product id. A product is part of an invoice line item if both have the same value for their product id. This relationship is expressed in Listing Nine . At execution time, the isIn function returns nonzero if the supplied product is in the given invoice line item. Otherwise, it returns zero. This isIn function can be defined outside either class Product or class LineItem. It depends on the linking or key attribute. In this case, the key attribute is Pid.

Static and Dynamic Relationships

If a relationship exists between two or more classes, then, at a minimum, there must exist a function to verify the relationship between the classes and use one or more attributes that the classes have in common. In the isIn example, the classes Product and LineItem have the Pid attribute in common, and it is valid C++ to write the function isIn.

During execution, if a given LineItem Pid value matches the Pid attribute value of a Product, then the corresponding invoice line item contains the product.

A static relationship always exists if classes have common attributes. A dynamic relationship exists during execution. Static relationships can be checked at compile time, while dynamic relationships need to be evaluated at run time.

Conclusion

There is a trade-off between classic C++ coding methods and DAN: A program using DAN has more classes but does not pollute the namespace as badly. That is, DAN overloads attribute names, thus avoiding the need for access functions like getX() and setX(), where X is some attribute name.

Using DAN to define a problem is more declarative in nature than most other C++ coding styles, yet, it leads more directly to an implementation, since you compile the specifications that you wrote. Because the step from specification to implementation is very short, the chance of errors and misunderstandings is smaller.

Listing One

// classic C++ style

class Product {
  long pid;
  char desc[30];
  double cost_price;
  double list_price;
};


Listing Two


class Pid {
  long p;
public:
  Pid(long pp = 0) { p = pp; }
  Pid(const Pid& pp) { p = pp.p; }
  Pid operator long() { return p; }
  Pid operator +(const long pp)
     { return Pid(p+pp); }
  Pid operator -(const long pp)
     { return Pid(p-pp); }
  friend ostream& operator <<
       (ostream& os, Pid& pp)
     { return os << pp.p; }
  friend istream& operator >>
       (istream& is, Pid& pp)
     { return is >> pp.p; }
};


Listing Three


class Product {
  Pid  p; // product code
  Desc d; // description
  Cost c; // cost price
  List l; // list price
public:
  Product(const Pid& pp,
          const Desc& dd,
          const Cost& cc,
          const List& ll)
     :p(pp), d(dd), c(cc), l(ll) { }
  ~Product() { }
  friend ostream& operator <<
     (ostream& os, Product& p);
  friend istream& operator >>
     (istream& is, Product& p);
}; 


Listing Four


Product p;  // product instance
p = 7;    // assign 7 to what?



Listing Five


// poor solution - do not use
class Product
{
public:
  Pid p;  // bad practise
  // ... other members
}
      Product aP;
      aP.p = 7;


Listing Six


// classic C++ coding style

class Product {
  Pid  p;
  Desc d;
  Cost c;
  List l;
public:
  Cost getCost() const
      { return c; }
  void setCost(const Cost& cc)
     { c = cc; }
  // ... other members
};
  Product aP;
  Cost aCost;
  aCost = aP.getCost();
  aP.setCost(aCost);


Listing Seven


// Data Attribute Notation style
class Product
{
  Pid  p; // product code
  Desc d; // description
  Cost c; // cost price
  List l; // list price
public:
  // insert & extract operators
  Product& operator <<(Pid& pp)      { p = pp; return *this; }
  Product& operator <<(Desc& dd)
     { d = dd; return *this; }
  Product& operator <<(Cost& cc)
     { c = cc; return *this; }
  Product& operator <<(List& ll)
     { l = ll; return *this; }
  Product& operator >>(Pid& pp)
     { p = pp; return *this; }
  Product& operator >>(Desc& dd)
     { dd = d; return *this; }
  Product& operator >>(Cost& cc)
     { cc = c; return *this; }
  Product& operator >>(List& ll)
     { ll = l; return *this; }
  // attribute conversion ops
  operator Pid()  const  
     { return p; }
  operator Desc() const
     { return d; }
  operator Cost() const
     { return c; }
  operator List() const
     { return l; }
  // constructors/destructor
  Product() { }
  Product(  const Pid& pp, 
            const Desc& dd,
            const Cost& cc, 
            const List& ll)
  : p(pp),d(dd),c(cc),l(ll) { }
  ~Product() { }
  // I/O stream friend functions
  friend ostream& operator <<
     (ostream& os, Product& pp);
  friend istream& operator >>
     (istream& is, Product& pp);
};
ostream& operator <<
  (ostream& os, Product& prod)
{
  os << " Prod: " << prod.p
     << " Desc: " << prod.d
     << " Cost: " << prod.c
     << " List: " << prod.l
  return os;
}
istream& operator >>
  (istream& is, Product& prod)
{
  is >> prod.p >> prod.d
     >> prod.c >> prod.l;
  return is;
}
int main() {
  Product prod;
  Pid pid(184389);
  Desc desc("Angle Bracket");
  Cost cost;
  // using insert/extract ops
  prod << pid << desc;
  prod >> desc >> pid;
  prod << Pid(34562);
  prod >> cost;
  // using conversion operators
  cout << " Prod: " <<Pid(prod)
       << " Desc: " <<Desc(prod)
       << " Cost: " <<Cost(prod)
       << " List: " <<List(prod)
       << endl;
  // using stream I/O
  cin >> prod;
  cout << prod;
  return 0;
}


Listing Eight


class LineItem {
  Iid  i;   // invoice id
  Pid  p;   // product id
  Quant q;  // quantity
public:
  // ... other members
};


Listing Nine


Bool isIn(Product& p,LineItem& i)
{
  return Pid(p)==Pid(i);
} 

Copyright © 1994, Dr. Dobb's Journal


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.