LETTERS

DAN Revisited

Dear DDJ,

I just read "DAN," by Reginald B. Charney (DDJ, August 1994), and I enjoyed it greatly. The code that would come up is wonderfully clear but...

Wouldn't using DAN discourage the reuse of code? In Reg's example, the basics of the classes Cost and List would be the same. In fact, it is conceivable that the code modules would be identical except for a Search-and-Replace of all instances of Cost with List.

The first answer that I see to that is "inheritance." However, although I've programmed in C++ classes for the last three years, I've never had to be concerned with speed and only a little concerned with size, so I've never checked whether or not inheritance (without virtual functions) adds any negative effects. I know virtual functions add space (the V-Table) and time (lookup in the table).

Darren

Germany

Reg responds: Thank you for the complement and questions. Both are very much appreciated. I will try to answer the two questions you raised.

First, "Does DAN discourage reuse of code?"; and second, "Does inheritance without virtual functions add any overhead?"

It is my experience and belief that DAN improves the reuse of code. Code is re-used when its purpose is clear and its interface is clean. Since DAN maps closely to your own requirements, it is easy to use a DAN class as a base for further specialization. Also, a DAN class's member functions tend to be simple and intuitive since most are based on the attributes of the class. For example, let us assume you sell a line of software and you intend to extend the line to include a "lite" and "pro" version. Let the class SKU (Stock Keeping Unit) be the base for all your products, and let us say it is defined as in Example 1.

As you can see, the base DAN class SKU has been used to build the derived DAN class CostedSKU. This simple example does not use virtual member functions, but in real life it probably would.

I was delayed in answering your question by a "problem" that I preceived with this solution. I needed to duplicate the operators << and >> from the base class SKU in the derived class CostedSKU. This seemed ugly. However, it is needed for two reasons.

The first reason is that while the operators << and >> are defined for base class B, they must return values of the type D&, where D is the derived-class type. For example, Example 2 would be invalid for two reasons. First, the result of d<<x1 would be a B& (not a D&, as desired); and second, the operator << in the preceding example is a member function, and the type of the first operand of the operator << would not be converted from a D& to a B&; thus, the operator B::operator <<() would not be invoked and we would have an undefined operation. (Making the operator<< a friend function is possible and at first glance overcomes the conversion problem; however, it involves so much extra work for a limited return that it was not viewed as a viable solution.)

The second reason for the "problem" is philosophical. Should the attributes of a base class be implicitly accessible in a derived class? While mechanically we may want to say yes, in general we want to say no, since the attributes making up the base class are usually hidden by the derived-class encapsulation. In the preceding example, is it normal to want to know the packing list on a CostedSKU as opposed to a basic SKU? If a car is composed of an Engine and Body, is it normal to ask the number of Cylinders the car has without first referring to the Engine of the Car. That is, while the expression Cylinders(Car) is possible, the expres-sion Cylinders(Engine(Car)) makes more sense. If the former is desired, then an explicit definition can be provided to convert a Car instance to the number of Cylinders the car engine has.

You also asked if classes do not contain virtual functions, is there any overhead? Under ideal conditions, the answer is no. However, this translates into a quality-of-implementation issue, and different compilers may produce slightly different results. However, it is my belief that eventually, optimized code for all major vendors will produce derived classes with no overhead if no virtual inheritance is used. (Compiling in debug information into a module may result in more overhead for derived classes, regardless of the presence or absence of virtual functions.)

Dear DDJ,

I just read the article, "Data Attribute Notation Relationships," by Reginald Charney (DDJ, January 1995), which is a follow-up on a prior article describing DAN. Neither article explores the downside to DAN's Generating Encapsulation Redundancy, or "DANGER."

As for data, a "root" class (or attribute class) contains a single data attribute. Non-root classes are used to provide additional data attributes, but no data attributes are allowed in nonroot classes. Therefore, a root class is defined (or redefined in this case) as a data attribute. Each nonroot Object must instantiate all of its encapsulated Objects, and each subsequent encapsulated Object then instantiates all of its Objects until a root Object is encountered. This could create quite a call stack for "actual" applications and would definitely create longer execution times.

Next, the original article stated a DAN benefit of not polluting the namespace as badly as with access functions. First, access functions are encapsulated within a class (if properly designed) and are available only to objects as defined in the interface, but classes are "global." Second, DAN pollutes the environment with classes which jeopardizes inheritance and reusability, not to mention conflicting class names for libraries and programs. For example, (DAN) Class X is the x coordinate of a point defined as a FLOAT, and by itself means what? Furthermore, defining Class X as an INTEGER doesn't make it more/less meaningful. (Non-DAN) Class X defined as the x chromosome does have meaning by itself. Continuing, (DAN) Class XYPoint is a class encapsulating two classes, Class X and Class Y, rather than containing two attributes, FLOAT x and FLOAT y. Of course, Class X and Class Y are entirely redundant as defined (to show the Class X conflict), so we change (DAN) Class X/Class Y to Class Point, and XYPoint now encapsulates Class Point x and Class Point y.

Some of the disadvantages to using DAN are:

Timothy D. Nestved

Orlando, Florida

Example 1: Test DAN and inheritance.

// test.cpp - test DAN and inheritance
#include <iostream.h>
#include <string.h>
class PartNumber {
    long n;
public:
    PartNumber(long nn=0) : n(nn) { }
    operator long() const { return n; }
};
class Media {
    char *cp;
public:
    Media(char* ccp=0) : cp(ccp) { }
    operator const char*() const { return cp; }
    // ... should also define copy constructor & assignment op
};
class PackingList {
    char *cp;
public:
    PackingList(char* ccp=0) : cp(ccp) { }
    operator const char*() const { return cp; }
    // ... should also define copy constructor & assignment op
};
class SKU {         // simplified Stock Keeping Class
protected:
    PartNumber pn;
    Media m;
    PackingList pl;
public:
    operator const PartNumber&() const  { return pn; }
    operator const Media&() const       { return m; }
    operator const PackingList&() const { return pl; }
    SKU& operator <<(const PartNumber& ppn){ pn = ppn; return *this; }
    SKU& operator <<(const Media& mm)      { m = mm; return *this; }
    SKU& operator <<(const PackingList& ppl){ pl = ppl; return *this; }
    SKU& operator >>(PartNumber& ppn)      { ppn = pn; return *this; }
    SKU& operator >>(Media& mm)            { mm = m; return *this; }
    SKU& operator >>(PackingList& ppl)     { ppl = pl; return *this; }
    friend ostream& operator <<(ostream& os, SKU& ss);
    // ... other public member functions
};
// Now let us define a CostedSKU class for use in sales and costing.
class Cost {
    double c;
public:
    Cost(double cc=0.0) : c(cc) { }
    operator double() const { return c; }
    friend ostream& operator<<(ostream& os, const Cost& cc);
};   // cost in some standard currency (dollars, DeutschMarks, etc.)
ostream& operator <<(ostream& os, const Cost& cc) { return os << cc.c; }
class SRP {
    double s;
public:
    SRP(double ss=0.0) : s(ss) { }
    operator double() const { return s; }
    friend ostream& operator<<(ostream& os, const SRP& s);
};    // Suggested Retail Price in some standard currency
ostream& operator <<(ostream& os, const SRP& ss) { return os << ss.s; }
class CostedSKU : public SKU {
    Cost c;
    SRP s;
public:
  operator const Cost&() const { return c; }
  operator const SRP&() const  { return s; }
  operator SKU&() const { return (SKU&)*this; }
  CostedSKU& operator <<(const Cost& cc)    { c = cc;   return *this; }
  CostedSKU& operator <<(const SRP& ss)     { s = ss;   return *this; }
  CostedSKU& operator >>(Cost& cc)          { cc = c;   return *this; }
  CostedSKU& operator >>(SRP& ss)           { ss = s;   return *this; }
  CostedSKU& operator <<(const PartNumber& ppn){ pn = ppn; return *this; }
  CostedSKU& operator <<(const Media& mm)    { m = mm;   return *this; }
  CostedSKU& operator <<(const PackingList& ppl){ pl = ppl; return *this; }
  CostedSKU& operator >>(PartNumber& ppn)    { ppn = pn; return *this; }
  CostedSKU& operator >>(Media& mm)          { mm = m;   return *this; }
  CostedSKU& operator >>(PackingList& ppl)    { ppl = pl; return *this; }
  // ... other public member functions
};
int main()
{
    CostedSKU cs;
    cs << PartNumber(123);
    cs << Media("3.5");
    cs << PackingList("Users' Guide");
    cs << PartNumber(123) << Media("3.5") << PackingList("Users' Guide");
    cs << Cost(18.34);
    if (strcmp(Media(cs),"CD-ROM") == 0)
        cs << SRP(Cost(cs)+5.00);  // SRP based on cost of CD-ROM
    else
        cs << SRP(Cost(cs)+10.00); // diskettes are more expensive than CD-ROM
    cerr << "SRP of SKU is " << SRP(cs) << endl;
        return 0;
}

Example 2: DAN #2.

class X;
class B { X x; public:
            B& operator <<(X); };
class D : public B { /* ... */ };
D d;
X x1, x2;
d << x1 << x2;


Copyright © 1995, Dr. Dobb's Journal