Gilbert Chen and Boleslaw Szymanski, Center for Pervasive Computing and Networking, Rensselaer Polytechnic Institute
Convenient and powerful as object-oriented programming is, it has its limits. One of these is that it often imposes unnecessary inter-object dependence on the deployment of objects that prevents objects from being reusable. As a small example, in the following diagram, an object A calls a method, g(), of another object B.

Object A must keep a pointer (or a reference) to object B in order to make such a method call. Let us assume that these two objects have been set up correctly in one program. The difficulty arises when A is to be reused in another program. Obviously, in such a case, either B must also be moved over to the new program, or there is another object that is derived from B available for A to access . If it is the former, things will become worse if some of B's methods are dependent on another object C. As a result, any object that A depends upon, either directly or indirectly, must be available in the new program.
Yet we only need, in the above example, a method that provides the same functionality as B.g() does. We should not be concerned with which object can provide such a functionality, whether it be B or a different, completely unrelated object D. In object-oriented programming, once you make an inter-object method call, you not only introduce explicit inter-object dependence manifested by the method call (represented by the solid arrow in the above diagram), but also implicit dependence that is hard to trace and maintain (represented by the dashed arrow).
The solution is to introduce inports and outports. An inport defines what functionality an object (now it is becoming a component) provides, and an outport defines what functionality it needs. From the diagram below, it is apparent that the implicit dependence has been removed. Another benefit of having inports and outports is that any interaction a component may have with other components can be deduced only from its interface (which is composed of declarations of inports and outports). In contrast, for an object the same information is clear only after scanning through the entire source code.
.
The central idea of CompC++ is to add inports and outports to objects to make them look and function like components. The extension to the standard C++ language is minimal: only 4 new keywords (component, inport, outport, and connect) and 4 new syntactic rules are needed. The addition of these, nevertheless, opens up a whole new programming paradigm, which is referred to as component-oriented programming.
The classic "hello world" example is too trivial a program to justify the usefulness and advantages of component-oriented programming. Still, some points can be made via this otherwise meaningless example.
In component-oriented programming, a program is always constructed by
composing components. Therefore, we must build a component that prints out
a given string on the screen.
component print_string
{
public:
inport void in(const char*s);
};
void print_string::in(const char*s)
{
printf(s);
}
This component has only one inport in() which prints out the given
string. You may notice that an inport is very similar to an ordinary member
function. In fact, the keyword inport can be removed-- its existence is
merely for the convenience of compilation. To the right is the
graphical representation of this component. 
component string_generator
{
public:
outport void out(const char* s);
void run();
};
#include <stdio.h>
void string_generator::run()
{
out("hello, world!\n");
}
The component string_generator defines an outport out() that outputs a string, as well as an ordinary function run() which calls the outport out() on the "hello world" string. Notice that the outport out() has no implementation. Actually, an outport can always be thought of and used as a function pointer, though they are not exactly the same.
Now we are ready to built the program. In the main() function, we simply instantiate one string_generator component and one print_string component, and then connect their inport and outport.
int
main()
{
string_generator g;
print_string s;
connect g.out, s.in;
g.run();
return 0;
}
Even in this small example the main benefit of component-oriented programming is evident: we are writing reusable components. The string_generator component can generate a "hello world" string and therefore can be reused in any program where such a string is needed. The print_string component is even more generic: it can be reused when a string needs to be printed out to the screen.
But how about efficiency? Does component-oriented programming incur any inter-component communication overhead? Not in CompC++. In fact, this is what distinguishes CompC++ from numerous other so-called component-oriented programming languages/approaches, such as ViusalBasic, C#, and Dephi. The CompC++ compiler will replace the function call to an outport with the function call to all inports that have been connected to the outport. For instance, the statement
out("hello, world!\n");
will be converted to:
p_print_string->in("hello, world!\n");
p_print_string is a pointer to a print_string component which has been initialized correctly by the CompC++ compiler. If the in() function of the print_string component was declared as an inline function, no extra communication overhead will be incurred.
Perhaps the most amazing feature of CompC++ is its support for component construction in a hierarchical way. A component that is implemented directly, as the above print_string and string_generator components, is called a primitive component. A component that is composed of smaller components is called a composite component. Below a composite component is created:
component hello_world
{
public:
string_generator g;
print_string s;
outport void out(const char*s);
hello_world ()
{
connect g.out, s.in;
connect g.out, out;
}
};
This composite component consists of two primitive components, g and s. It also has an outport out() which is connected to the outport out() of the primitive component g of type string_generator. Hence, the hello_world component behaves very much like a single string_generator component, in that it too provides a "hello world" string upon invocation. However, the "hello world" string will also be printed out by its own print_string component, s (which one comes first is implementation-dependent). The function below will then display the "hello world" string twice.
int main()
{
hello_world h;
print_string s;
connect h.out, s.in;
h.g.run();
return 0;
}
In many situations, we may need a variable number of components or outports (inport arrays may not be as much needed, as a single inport can act as an inport array). CompC++ provides somewhat awkward (albeit effective) syntax for defining these arrays. The following program is an example.
#include <stdio.h>
component A
{
public:
inport void in();
outport void out(int i);
int id;
};
void A::in()
{
out(id);
}
Component A is an absolutely normal component, which, upon the invocation of the inport in(), will output its id via the outport out().
component B
{
public:
void run();
outport[] void out();
inport void print(int i);
void config(int _n);
private:
int n ;
};
void B::config(int _n)
{
n=_n;
out.SetSize(n);
}
void B::print(int i)
{
printf("%d\n",i);
}
void B::run()
{
for(int i=0;i<n;i++)
out[i]();
}
Component B contains an outport array out[](), so out[i]() for
any integer i would appear as a regular outport. To correctly use
an outport, a special member function named SetSize() must be called
which sets the size of the array. This component also defines an inport
print() which would print out any integer it receives, and a normal function
run() that invokes every outport in the outport array one by one.
int main()
{
A[] a;
B b;
int n=10;
a.SetSize(n);
b.config(n);
for(int i=0;i<n;i++)
{
a[i].id=i;
connect a[i].out,b.print;
connect b.out[i],a[i].in;
}
b.run();
return 0;
}
In the main() function, an array of component A is initialized followed by a single instance of the component B. Similar to an outport array, a component array must also be initialized by the special function SetSize(). The rest of the function is easy to understand, if we remember that each element of an outport array or a component array can always be accessed individually by providing an appropriate index.
So how do you think of CompC++? Isn't it interesting? The CompC++ compiler is now released with SENSE, a wireless sensor network simulator. After downloading SENSE and compiling it, you can find the CompC++ compiler under the directory bin.