hypertherm class library

23
(Work in progress) HyClass: The Hypertherm C++ Class Library Introduction Timely development of good C++ applications requires a solid foundation of reusable code. The "HyClass" class library strives to achieve this goal. Quick Start Please see "Precompiled.Header.h" in ".\HyClass\ Documentation". HyClass: Hypertherm Class Library © Hypertherm, Inc. 1999. 1

Upload: fernandoj

Post on 14-May-2017

222 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Hypertherm Class Library

(Work in progress)

HyClass: The Hypertherm C++ Class Library

IntroductionTimely development of good C++ applications requires a solid foundation of

reusable code. The "HyClass" class library strives to achieve this goal.

Quick StartPlease see "Precompiled.Header.h" in ".\HyClass\Documentation".

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 1

Page 2: Hypertherm Class Library

General Characteristics of HyClass

Aims for the highest standards of correctness, simplicity, readability, efficiency, reusability and portability.

Written in ANSI/ISO C++. This means the Standard C++ Library, namespaces, exceptions, iterators, etc.

Depends on the Win32 API, but nevertheless strives to be portable between Windows 98/NT/CE. Hence we often add small wrapper classes.

As far as possible, doesn't depend on Microsoft's MFC. This is to try to keep lightweight and portable.

Not Unicode-hostile. Is not fully Unicode, because of limitations imposed by the Win32 API, MFC, and the C++ Standard Library, but nevertheless tries to avoid being an obstacle for Unicode.

Extensive code samples are provided so any class can be used quickly and easily.

Depends heavily on CHyEx, the exception class. Also depends heavily on std::string. These important design decisions are justified below.

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 2

Page 3: Hypertherm Class Library

Contents

Each subdirectory of "HyClass" (except "Documentation" and "UML_Diagrams") is a class package, and each class package has a corresponding UML diagram giving an overview of its purpose and contents.

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 3

Page 4: Hypertherm Class Library

Thoughts

describe HyClass, etc.

Must include automated regression suite to test HyClass itself! Maybe add one ITestCase object per class, or component?

Damn Strings:

WARNING! I've wasted far too much time with this already.

I'm trying to avoid hard-coding MFC::CString everywhere.

At first, I hard-coded std::wstring everywhere, thinking it was part of the C++ language and gave us Unicode to boot.

Then I just kept running into problem after problem because of the wide chars.

So I created my own "HyClass::str", which was a thin wrapper around MFC::CString.

But then I just got hammered by : (0) which interface do we support, std::string or MFC::CString? (1) "Prefer the Standard to the Offbeat" says Bjarne; (2) std::string has many functions I need, plus the style of containers I want to promote...

SOOOO, I decided to change once again and put everything as std::string... God help me.Anyway I feel much safer betting the company on a standard C++ library class than my own. Yes!

"Design Decisions" for "HyClass::str".- Name. Very, very short because used very often. It also looks likea predefined C++ type because THAT IS WHAT C++ SHOULD GOLDARN HAVEHAD, DAMMIT!- Yet another string class? Unfortunately seems necessary: (0) allthese stupid string classes ignore each other's existance! I need onethat can convert to any other one; (1) I don't want to lock theHyClass library into CString, or std::wstring, etc.; (2) I'm not tryingto reinvent the string implementation, and I'll probably use CStringor something; (3) somebody has to hide all that silly garbage about ANSIIand Unicode, MFC::CString and std::string or std::wstring, constchar* and const wchar_t*, etc.

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 4

Page 5: Hypertherm Class Library

I also tried this on 22-Sep-2000

/**********************************************************************str

Responsibilities:- Be like "std::string", but eliminate the typedef so error messageswill be shorter, and enable Microsoft "intellisense" mechanism toselect functions for this class.- Eventually, might help us merge Unicode into HyClass (I haven'tbeen able to do it yet).

WARNING!!!This whole string story is a huge, enormous problem. C++ simply doesn't

have a good string class, the one it does have is not universally used,the mix of Unicode and ASCII is not handled, etc.

**********************************************************************/class str: public std::string{public:// Ctors and dtor

// We were forced to duplicate all these ctors because of C++ stupidity.str(): string() {}str(const string& s): string(s) {}str(const char* begin, const char* end): string(begin, end) {}str(const int length, const char filler): string(length, filler) {}

// Can't use "assign" because already taken.void fromString(const string& s) { string::operator=(s); }

// (Notice we fix bug when an std::string is initialized with null)str(const char* s): string((s) ? s: "") {}

};

But this caused all kinds of problems, and didn't bring me any closer to Unicode. I wish I hadn't checked everything in. It changed all the files, and didn't buy me anything.

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 5

Page 6: Hypertherm Class Library

CEx

I used to have an exception class, but I removed it:- caused many unecessary dependencies for people trying to use HyClass (like Win32 for translating MS exceptions, or std::ostringstream for the operator<<(), etc.)- I was using the offbeat instead of the standard.- I was under the mistaken impression that C++ didn't define exceptions (but it does, in <stdexcept>)- I tried to add an error code in the exception class, so I'd be able to have inheritance + enums to identify problems, and to aid error message translation. But this fails when 2 exceptions classes each adding enum values to a base exception class with enum values have overlapping enums (inevitable, since the siblings can be coded by different people, at different times, etc.) Only catching the base class and dynamic_cast-ing can trace the actual type. So the built-in mechanism is actually better (and less work).

The sad part is this caused major upheaval in the HyClass framework and the RoboTester... Pride cometh before a fall!

/**********************************************************************CEx

Responsibilities:- Small exception class, used extensively by the HyClass library.- See [Stroustrup1997], p. 356-388 to better understand theresponsibilities of this class, as well as general considerations forexception handling, assertions, class invariants, etc..- Permit reporting of exceptional occurences to another part of thesystem that knows how to handle them.- Exceptions are for EXCEPTIONAL conditions! If something canbe taken care of locally, do so!

Code Samples - Including the Header:// Add this where exceptions are thrown and caught:

using CEx;

Code Samples - Throwing an exception:void Foobar(){

// Lets assume something went terribly wrong.throw CEx("I can't deal with this problem");

}

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 6

Page 7: Hypertherm Class Library

Code Samples - Catching an exception:// ...Meanwhile, somebody higher up the call stack is willing// to handle this hot potato:try{

Foobar();}catch(const CEx& e){

::AfxMessageBox(e.GetErrorMsg().c_str());}

Code Samples - Trapping Microsoft Exceptions:

BOOL CMyApp::InitInstance(){

// Make sure Microsoft's exceptions will be translated into ours.CEx::TrapMicrosoftExceptions();

// ... rest of code as usual.}

Code Samples - Checking Preconditions:void CMyClass::MyFunction(){

CEx::Precondtion(2 + 2 == 5, "Bad Precondition");}

Code Samples - Building complex error messages:CEx ex("Really bad vibes in CMyClass::FooBar(): ");ex << "str: " << str << ", int: " << 123 << ", double:" 0.987;throw ex;

Code Samples - Creating derived exception classes with error codes:// See "HyClass::Communication::CCommError"

Tips:- When debugging, call "SetBreakPointInCtor()" to hit a breakpoint before the actualexception is thrown. This prevents the loss of the stack frame andcontext in which the exception occured.- You can override "CWnd::OnCommand()", to add a try-catch block aroundthe base-class function call. This means you can trap exceptionsthrown in any MFC message handler (thanks to Steve Brandt for this idea).

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 7

Page 8: Hypertherm Class Library

Error-Code Idiom:- I'm trying an idiomatic use of this exception class, which is motivatedby: (1) the necessity to report specific error messages for veryspecific errors encountered in classes which have not even been writtenyet; (2) the necessity of avoiding a monster ".h" file containing allerror codes upon which all other files must depend (See [Lakos 1996], p. 8-9);(3) the necessity to translate error messages, sometimes; (4) thefundamental truth that "goodness is one, error is infinite", i.e. thatfor something to work, not a single error must occur, but the number ofthings that can go wrong is infinite. In other words, modeling eachand every error as a distinct class necessarily leads to abominably hugeexception class hierarchies.- The solution I'm trying is to include a "number" member variable inthe base class, so when you develop some sub-system, you create a newexception class derived from "CEx", and create some "enum" of your specificerror codes. Those values will go in the "number" member. So somebodywho ignore your derived class can still read the str error message,but somebody who is prepared to catch your specific exception can justlook at the number. So files still depend on the ".h" file of yourspecific exception class, but far fewer ones.

**********************************************************************/class CEx : public std::exception{public:// Ctors

CEx() throw ();CEx(const char* errorMsg, const int errorNo = 0) throw (); CEx(const str& errorMsg, const int errorNo = 0) throw (); CEx(const CEx& rhs) throw ();virtual ~CEx() throw();

// Services// Let exceptions continue, instead of stopping on breakpoint.// (By default, we don't set it). This is very useful to find bugs!static void SetBreakPointInCtor(const bool isStopCtor = true)

{ m_isStopCtor = isStopCtor; }

// Throw a CEx exception if the assertion fails. The other// functions just call assert, but make the intention clearer.static void Assert(

const bool isConditionTrue,const char* errorMsg,const int errorNo = 0);

static void Precondition(const bool isConditionTrue,

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 8

Page 9: Hypertherm Class Library

const char* errorMsg,const int errorNo = 0)

{ Assert(isConditionTrue, errorMsg, errorNo); }static void Postcondition(

const bool isConditionTrue,const char* errorMsg,const int errorNo = 0)

{ Assert(isConditionTrue, errorMsg, errorNo); }static void Invariant(

const bool isConditionTrue,const char* errorMsg,const int errorNo = 0)

{ Assert(isConditionTrue, errorMsg, errorNo); }

// Add more information to the error message.CEx& operator<<(const str& str);CEx& operator<<(const char* str);CEx& operator<<(const int i);CEx& operator<<(const unsigned int i);CEx& operator<<(const double d);

// Accessors// Return the raw str error message of this exception.virtual const char* what() const throw();virtual const str& GetErrorMsg() const throw();

// Return the error number of this exception (normally for// translation of the error message into other languages).virtual int GetErrorNo() const;

// See related Service.static bool GetBreakPointInCtor()

{ return m_isStopCtor; }// Modifiers

CEx& operator=(const CEx& rhs) throw ();

private:// Helpers

// Stop execution of the ctor so we don't lose the call// stack and debugging context.void StopCtor() const;

// Data// See related Accessors.str m_errorMsg;int m_errorNo;

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 9

Page 10: Hypertherm Class Library

// Application-wide flag used mainly for debugging.static bool m_isStopCtor;

// Regression Testsfriend class CTestCase_Ex;

};// Includes#include "StdAfx.h"

// For convenience "<<" functions.#include <sstream>

// For regression tests only#ifdef __HYCLASS_REGRESSION_TEST__

#include "WindowProgress.h"#endif

// Namespace managementusing std::ostringstream;using namespace HyClass;

/**********************************************************************CEx

Design Decisions:- Name. As usual, "CException" and other normal names are taken,so we add a prefix. I used to call it "CHyEx", giving it a very shortname because it is used very often, but with "CException::Assert()",that is less true, and a clear class name takes precedence. And thenI changed it again to make it short once more... :-)- Inheritance from std::exception. Seemed the best thing to do, sowe can "catch" all standard C++ exceptions.- Dynamic memory allocation. It is a bad idea in an exception class,but really simplifies it, since we usually add strings, etc. I've written many exception classes, and avoiding "std::str" makes thingsunnecessarily painful and complex.- String translation. Ouch! Best we can do for now is to includea number which can be used to uniquely identify the translatable str.- Outstreamer. I used to have:

// Outstreamer.// (See [Stroustrup1997], p. 612)virtual std::ostream& Print(std::ostream& os) const throw();

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 10

Page 11: Hypertherm Class Library

friend std::ostream& operator<<(std::ostream& os, const CWinException& rhs){ return rhs.Print(os); }

std::ostream& WinOS::CWinException::Print(std::ostream& os) const throw(){

os << what() << ", " << where();return os;

}

But it is used very rarely, added complexity, and has added unacceptabledependencies in some past projects ("MSVCP50.dll" at Lockheed, etc.).- "::_set_se_translator()". I successfully trapped Microsoft exceptionsinstead of catching them as bland "(...)" exceptions, but this featurewas never really useful since I figure out how to have the debugger halton access violations, etc. Also, this feature caused a big dependencyon Win32 headers in THE most basic HyClass class (the exception class)so I removed them. The code is still at the bottom of the file.

**********************************************************************/

// Static Data//////////////////////////////////////////////////

bool CEx::m_isStopCtor = false;

// Ctors//////////////////////////////////////////////////

CEx::CEx() throw ():

m_errorMsg("Unknown Internal Error"),m_errorNo(0)

{StopCtor();

}

CEx::CEx(const char* errorMsg, const int errorNo /*= 0*/) throw ():

m_errorMsg(errorMsg),m_errorNo(errorNo)

{StopCtor();

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 11

Page 12: Hypertherm Class Library

}

CEx::CEx(const str& errorMsg, const int errorNo /*= 0*/) throw ():

m_errorMsg(errorMsg),m_errorNo(errorNo)

{StopCtor();

}

CEx::CEx(const CEx& rhs) throw ():

m_errorMsg(rhs.m_errorMsg),m_errorNo(rhs.m_errorNo)

{StopCtor();

}

CEx::~CEx() throw(){}

// Services//////////////////////////////////////////////////

void CEx::Assert(const bool isConditionTrue,const char* errorMsg,const int errorNo /*= 0*/)

{// If the assertion fails, throw an exception. Else ignore.if(!isConditionTrue){

throw CEx(errorMsg, errorNo);}

}

CEx& CEx::operator<<(const str& str){

m_errorMsg += str;return *this;

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 12

Page 13: Hypertherm Class Library

}

CEx& CEx::operator<<(const char* str){

m_errorMsg += str;return *this;

}

CEx& CEx::operator<<(const int i){

ostringstream os;os << i;m_errorMsg += os.str();return *this;

}

CEx& CEx::operator<<(const unsigned int i){

ostringstream os;os << i;m_errorMsg += os.str();return *this;

}

CEx& CEx::operator<<(const double d){

ostringstream os;os << d;m_errorMsg += os.str();return *this;

}

// Accessors//////////////////////////////////////////////////

const char* CEx::what() const throw(){

return m_errorMsg.c_str();}

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 13

Page 14: Hypertherm Class Library

const str& CEx::GetErrorMsg() const throw(){

return m_errorMsg;}

int CEx::GetErrorNo() const{

return m_errorNo;}

// Modifiers//////////////////////////////////////////////////

CEx& CEx::operator=(const CEx& rhs) throw (){

if(this != &rhs){

m_errorMsg = rhs.m_errorMsg;m_errorNo = rhs.m_errorNo;

}return *this;

}

// Helpers//////////////////////////////////////////////////

void CEx::StopCtor() const{

// If the user is debugging and wants to keep the call stack and// other debugging information...if(m_isStopCtor){

// Hello fellow programmer!// Please crawl the call stack to see where this exception was thrown.// Another great tip is go in the "Debug\Exceptions" menu (only visible// while debugging), and blocking access violations or others. This// can nail down the source of the exception even more precisely.__asm { INT 3 };

}// Else, just let the exceptions do their thing.

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 14

Page 15: Hypertherm Class Library

}

// Regression Tests//////////////////////////////////////////////////

#ifdef __HYCLASS_REGRESSION_TEST__

void CTestCase_Ex::Run(std::ostream& log, HyClass::IProgress& progress){

// Setup progress indicator.progress.SetSubTaskSteps(4);

// Remember what the previous value was for exception stopping.const bool previousValue = CEx::GetBreakPointInCtor();

// For this regression test, we don't want to block exception ctors!CEx::SetBreakPointInCtor(false);

// Test case:progress.StepAndLabelSubTask("Test all ctors: default, C str, C++ str, copy");CEx defaultCtor;CEx CstringCtor("CstringCtor");str cppString("CppStringCtor");CEx CppStringCtor(cppString);CEx copyCtor(CppStringCtor);

// Test case:progress.StepAndLabelSubTask("Test adding data to the error message: C++ str,

C str, int, double");CEx operatorPutTo("A");str letterC("C");operatorPutTo << "B" << letterC << 1 << 2.0;CEx::Assert(operatorPutTo.GetErrorMsg() == "ABC12", "Bad message

additions");

// Test case:progress.StepAndLabelSubTask("Test assignment operator");defaultCtor = operatorPutTo;

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 15

Page 16: Hypertherm Class Library

CEx::Assert(defaultCtor.GetErrorMsg() == "ABC12", "Bad assignment operator");

// Reset the previous value for exception stopping.CEx::SetBreakPointInCtor(previousValue);

}

#endif

// Old Code//////////////////////////////////////////////////

#if 0

// Call this function at the beginning of every thread to transform// Microsoft's exceptions into CEx exceptions.// See Jeffry Richter, Programming Applications for Microsoft// Windows, 4th Ed.", p. 906.)static void TrapMicrosoftExceptions();

// Internal Linkage Free Functions//////////////////////////////////////////////////

// For Microsoft exceptions// (See also "CEx::TrapMicrosoftExceptions()".)#include <eh.h>

namespace {

// Convert Microsoft exceptions into our exceptions.// (See also "CEx::TrapMicrosoftExceptions()".)static void _cdecl TranslateMicrosoftExceptionIntoOurs(

unsigned int dwEC,PEXCEPTION_POINTERS pep)

{// Build error message depending on the type of error.const int exceptionCode = pep->ExceptionRecord->ExceptionCode;std::ostringstream errorStream;if(EXCEPTION_ACCESS_VIOLATION == exceptionCode){

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 16

Page 17: Hypertherm Class Library

errorStream << "Attempt to access invalid memory (access violation)";

}else if(EXCEPTION_INT_DIVIDE_BY_ZERO == exceptionCode){

errorStream << "Attempt to divide by zero";}else{

errorStream << "Unknown Microsoft Exception. ExceptionCode: " <<

exceptionCode;}// Maybe someday add the rest of the information contained// in "pep".

// Propagate exception, but now with proper type, and stoppable// in ctor (to be debugger-friendly).throw CEx(errorStream.str());

}}

void CEx::TrapMicrosoftExceptions(){

// Hook our function into Microsoft's system.// (See also "CEx::TrapMicrosoftExceptions()".)::_set_se_translator(TranslateMicrosoftExceptionIntoOurs);

}

#endif

HyClass: Hypertherm Class Library© Hypertherm, Inc. 1999. 17