This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

| A Simple Association Template for C++ Submitted by

At my previous employer, we used visual development tools to take UML and generate source code to implement some of the more complex relationships that UML can define but C++ doesn't have built in to the language. When I moved to my new employer, I found myself particularly missing simple associations, which are, essentially, a two-way pointer. So I set out to write up code to model them in C++ using templates.
A simple association is a simple, but powerful design tool and having an efficient implementation can be quite helpful. This template implements a simple (one to one) association. It can be used with other container classes to implement multiple or complex associations. I have included a sample main program which creates some dummy classes and tests things out. I figured that this code is something that not too many C++ developers have ever considered doing, but might find very useful. Enjoy.
Greg Taylor
gtaylor@falcon.csc.calpoly.edu

Currently browsing [assoc.zip] (2,870 bytes) - [assoc.h] - (3,586 bytes)

    // assoc.h
    //
    // A simple association template
    //
    // Provides a simple 1 to 1 association between two container classes.  If
    // one end of the association is set or broken, the other end is updated to
    // match.  Assertions check that the application programmer doesn't attempt
    // to double-set an association and other failure states.
    //
    // Instanciation:
    // Provide a member in each container class of type
    //   assoc<myContainer type, associatedContainer type>
    //
    // This member must be constructed with its parameterized constructor,
    // which takes a single parameter, a pointer to the container class (this).
    //
    // Public methods:
    //
    // link(assoc<other,my> *otherEnd) - This method sets up the linkage between
    //   the two container classes.  Its argument is a pointer to the association
    //   member in the other class.  Calling this method on one end of the
    //   association will set up both ends, so it only need be called once per
    //   association.
    //
    // unlink(assoc<other,my> *otherEnd) - De-associates the two container classes.
    //   There are two versions of this method, one with a parameter, which is
    //   simply the same pointer that was used to set up the association (a pointer
    //   to the other end) and one without.  The parameterized version is safer to
    //   use simply because it hs more assertion error checks, which may help to
    //   catch bugs earlier.
    //
    // Lastly, there is an overloaded type-cast for the association class to the
    // type of the associated container.  This is useful for preserving the abstracted
    // concept of the association.  It will return NULL if no association is currently
    // set. The 'get' method returns the same thing.

    #ifndef SIMPLE_ASSOCIATIONTEMPLATE
    #define SIMPLE_ASSOCIATIONTEMPLATE

    #include <assert.h>

    #ifndef NULL
    #define NULL 0
    #endif

    template <class myContainer, class otherContainer>
    class assoc
    {
      private:
        myContainer *containedIn;
        assoc<otherContainer, myContainer> *linkedTo;

        void linkToMe(assoc<otherContainer, myContainer> *otherEnd) {
          assert(otherEnd != NULL);
          assert(linkedTo == NULL);

          linkedTo = otherEnd;
        }

        void unlinkFromMe(assoc<otherContainer, myContainer> *otherEnd) {
          assert(otherEnd != NULL);
          assert(linkedTo == otherEnd);

          linkedTo = NULL;
        }

        myContainer *getContainer() {
          return containedIn;
        }

      public:
        assoc(myContainer *myOwner) {
          assert(myOwner != NULL);
          containedIn = myOwner;
          linkedTo = NULL;
        }

        ~assoc() {
          if (linkedTo != NULL) {
            linkedTo->unlinkFromMe(this);
          }
        }

        void link(assoc<otherContainer, myContainer> *target) {
          assert(target != NULL);
          assert(linkedTo == NULL);

          linkedTo = target;
          target->linkToMe(this);
        }

        void unlink(assoc<otherContainer, myContainer> *target) {
          assert(target == linkedTo);
          assert(linkedTo != NULL);

          target->unlinkFromMe(this);
          linkedTo = NULL;
        }

        void unlink() {
          // The unlink method with a parameter is prefered because it does additional
          // error checking.  Either method will accomplish the same task, however.
          assert(linkedTo != NULL);

          linkedTo->unlinkFromMe(this);
          linkedTo = NULL;
        }

        operator otherContainer *() {
          if (linkedTo == NULL)
            return NULL;
          return linkedTo->getContainer();
        }

        otherContainer *get() {
          if (linkedTo == NULL)
            return NULL;
          return linkedTo->getContainer();
        }

        friend class assoc<otherContainer, myContainer>;
    };

    #endif

Currently browsing [assoc.zip] (2,870 bytes) - [AssocTestMain.cpp] - (2,293 bytes)

    // Simple association tester and example usage code
    #include "assoc.h"
    #include <stdio.h>

    // two dummy classes that might have an association to each other.
    class Edge;

    class Vertex
    {
    public:
      float x, y, z ;
      assoc<Vertex,Edge> edge;

      Vertex() : edge(this) {
        x = 0.0f;
    	y = 0.0f;
    	z = 0.0f;
      }
    };

    class Edge
    {
    public:
      float angle;

      assoc<Edge,Vertex> vertex;

      Edge() : vertex(this) {
        angle = 0;
      }
    };

    // Test/example usage 'main' for simple associations.
    int main ()
    {
      Vertex v1;
      Edge e1;
      Vertex *pv;
      Edge *pe;

      // Set up an edge and vertices
      v1.x = 24.0f;
      v1.y = 14.0f;
      v1.z =  4.0f;
      e1.angle = 0.5;

      // Associate v1 and e1
      v1.edge.link(&e1.vertex);

      // Test association v1/e1 directly
      printf("This should be %g => %g\n", v1.x, e1.vertex.get()->x);
      printf("and this should be %g => %g\n", e1.angle, v1.edge.get()->angle);

      // That interface can be clumbsy so it can be cleaner to use the
      // overloaded cast operation.
      float i = ((Edge *)v1.edge)->angle;

      // Or for the cleanest referencing, use pointers
      pv = e1.vertex;
      pe = v1.edge;

      // Print out verification of expected results
      printf("This should be %g => %g\n", v1.y, pv->y);
      printf("and this should be %g => %g\n", e1.angle, pe->angle);

      // Now test clearing the association
      pe->vertex.unlink(&pv->edge);

      if (e1.vertex == NULL && v1.edge == NULL)
        printf("We successfully cleared the association\n");
      else
        printf("Test FAILED.\n");

      // Re-create the association.
      v1.edge.link(&e1.vertex);

      // Now break it another way
      v1.edge.unlink();

      // Test again
      if (e1.vertex == NULL && v1.edge == NULL)
        printf("We successfully cleared the association\n");
      else
        printf("Test FAILED.\n");

      // Now test the destructor
      Vertex *v2 = new Vertex;

      v2->z = 3.1415f;
      v2->edge.link(&e1.vertex);
      pv = e1.vertex;

      // Print out verification of expected results
      printf("This should be %g => %g\n", v2->z, pv->z);
      printf("and this should be %g => %g\n", e1.angle, pe->angle);

      delete v2;

      if (e1.vertex == NULL)
        printf("We successfully cleared the association\n");
      else
        printf("Test FAILED.\n");

      return 0;
    }

The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.