Written by Vladimir Belkin

E-mail: koyaanisqatsi@narod.ru

Yet another approach to C++ exceptions.

    When a callee can not complete required operation it may throw an exception. The caller, possible indirect, should choose to try to eliminate the obstacle that balks to complete the required operation and then retry the call or desist from the attempts. The caller should have enough information on the anomalous situation to arrive at the right decision. Sometime it is possible to query some of the required information from the object which method has thrown the exception, but in most cases only exception object can pass this information. C++ exception handling facility was designed to provide separating of regular algorithms from reactions on anomalous situations. This allows to have large enough pieces of code without explicit error checking; this is one more reason to make exceptions more informative.

    An exception object can pass the information on the situation cause by the two ways: in the exception class and in the object state. Only information passed by the first way can be used to design group exception handlers. Elaborated hierarchy of exception classes can facilitate structuring of exception handler sequences.

    Exceptions can be classified by various criteria that reflect different aspects of the exception causes. These criteria may include the encapsulated system resource, the stage of the resource life-cycle, the suggested operation on the resource, underlying API call and the returned status, the class and method that has thrown the exception, stage of the object existence, etc. The classification by each criterion like this can produce separated tree of classes. It could be fetching to map the resulting hierarchy straightforwardly on the hierarchy of C++ exception classes, using multiple inheritance, but the resulting hierarchy may become too complicated for implementation and use. Other approach involves partial mapping of the analysis hierarchy on the state of exception objects, but this approach hinders handler-sequences structuring.

    This paper describes yet another approach that is a variation of the straightforward mapping way with applying of special techniques that allow sufficient reduction of the number of explicitly defined C++ exception classes.

    From the mathematical point of view the phrase "A inherits from B, C and D" means that A is a subset of intersection of sets B, C and D: A<=B*C*D. Usually heir classes inherit some data or behavior, but an exception class can use inheritance relation to express the subset relationship, because C++ exception handling facility assumes the use of the mathematical effect of inheritance to define generic exception handlers.

    Described approach assumes implicit defining of most derived exception classes to throw the instances of abstract intersections of mixins; the derived classes name are never used explicitly. The special kind of virtual multiple inheritance, where an heir class adds no members to the heritage and order of bases has no value, is used to provide this implicit defining.

    I defined the family of overloaded template functions "throw_exception" for different number of arguments. Every argument type of a function from the family is a particular parameter of the function template. All these functions do the same thing:

  1. 1. Derive an exception class from the classes of actual parameters of the
  • call;
    1. 2. Create an instance of the class assigning the values of the call arguments
  • to the corresponding base subobjects of the instance;
    1. 3. Throw the built exception object.

        To handle exceptions, thrown by the described way, I defined the exception class sensitive predicates "thrown" and "caught". These predicates match ancestors of a caught exception class. To match a caught exception class with several ancestors, it is possible to build an expression from these predicates. Also it is possible to use these predicates to facilitate structuring of the handler-sequences in the traditional approach to the exception throwing.

        Consider the elaborated hierarchy of a library thrown exceptions. If the library works with file system, ODBC, and WWW then there could be grouping exception classes like listed below.
     

         struct X_File {};
         // X_File is a set of all file exceptions may be thrown.
         struct X_ODBC {};
         // X_ODBC is the set of all ODBC exceptions.
         struct X_DSN:public virtual X_ODBC {};
         // X_DSN is the set of all DSN exceptions.
         struct X_Internet {};
         // X_Internet is the set of all Internet exceptions.
         struct X_Host {};
         // X_Host is a set of all network host
         // exceptions may be thrown.
         struct X_WWW {};
          // X_WWW is a set of all possible WWW related exceptions
         struct X_URL:public virtual X_WWW {};
         // X_URL is a set of all possible URL related exceptions.
         struct X_AccessRightViolation {};
         // X_AccessRightViolation is a set of all exceptions
         // could be thrown on the access right violation.
         struct X_NoSuchResource {};
         // X_NoSuchResource is a set of all possible exceptions
         // could be thrown on the "resource not found" situation.

        When these mixin classes are defined it is possible to use their intersections as actual situation exception classes. For example, it is possible to use X_NoSuchResource*X_File as a class for the situations "No such file".

        There could be additional mixins to specify various attributes of exception, library specific and library independent, particularly there could be a class like X_ProseDescription to pass a prose description of the exception.
     

    class X_ProseDescription {
    const char *description;
    public:
      X_ProseDescription(const char *prose=0): description(prose) {}
      const char *what() const { return description; }
    };

      You can see that given classes are not specific for a particular library or an application. All libraries that use these kinds of resources could use them. It is possible to specify one standardized set of exception mixin classes. The use of set like this can improve the library collaboration and help to port the client code among several environments.

      Here is an example that demonstrates described techniques.
     

    try {
      // For simplicity I throw an exception of class
      // X_File*X_ResourceNotFound*X_ProseDescription
      // right here.
      throw_exception(
          X_File(), X_ResouceNotFound(),
          X_ProseDescription("File not found")
      );
    } catch (…) {
       // The most generic handler advisedly used here
       // to write more simple example.
       if (thrown<X_File>()&&thrown<X_NoSuchResource>())
       { // The exception will be handled here
          cought<X_ProseDescription> prose;
          cerr<<prose->what()<<'\n';
       } else throw;
    };

       An instance of the template class "thrown" behaves as a predicate that returns true only when an ancestor of caught exception class matches the template parameter. On attempt to create an instance of this template class out of any exception handler the function "terminate" will be called. The class "caught" provides all functionality of the class "thrown" moreover provides access the corresponding caught exception subobject by the operators "()" and "->".

      Listings of described tools are below.

    // exception class sensitive predicates
    template<class T>
    class thrown {
      bool is_thrown;
    public:
       operator bool () const { return is_thrown; }
       thrown() {
         try { throw;
         } catch (T) { is_thrown = true;
         } catch (...) { is_thrown = false;
         }
       }
    };
    template<class T>
    class caught {
       T *p;
    public:
       operator bool () const { return p!=0; }
       struct X {};
       // Exception of the class X will be thrown
       // on attempt to access a subobject which
       // has not been matched.
       T &operator () () throw(X) {
           if (p==0) throw X();
           return *p;
       }
       T *operator -> () throw(X) {
          if (p==0) throw X(); return p;
       }
       caught() {
         try { throw;
         } catch (T &tmp) { p=&tmp;
         } catch (...) { p=0;
         }
       }
    };
    // Helper template classes 'X_n' where 'n' is number
    // of arguments of corresponding template.
    //
    //
    // These template classes are used to define
    // 'throw_exception' template function family.
    template<class T1,class T2>
    struct X_2:
      public virtual T1,
      public virtual T2
    {
       X_2( const T1 &r1,const T2 &r2):
       T1(r1),T2(r2) {}
    };
    template<class T1,class T2,class T3>
    struct X_3:
      public virtual T1, public virtual T2,
      public virtual T3
    {
       X_3( const T1 &r1,const T2 &r2, const T3 &r3 ):
          T1(r1),T2(r2),T3(r3) {}
    };
    // Put definitions of 'X_n' where 'n' in 4..10 here.
    // Use scripts for such purposes.

     

    template<class T1,class T2>
    void throw_exception(const T1 &r1,const T2 &r2) {
       throw X_2<T1,T2>(r1,r2);
    }
    template<class T1,class T2,class T3>
    void throw_exception(const T1 &r1,const T2 &r2,const T3 &r3) {
       throw X_3<T1,T2,T3>(r1,r2,r3);
    }
    // Put 4..10 argument versions of 'throw_exception' here.
    Сайт управляется системой uCoz