///////////////////////////////////////////////////////////////// // trace.cpp - demonstrates use of Open/Closed Principle // // building a debugging trace facility. // // // // Jim Fawcett, CSE687 - Object Oriented Design, Spring 2008 // ///////////////////////////////////////////////////////////////// // // // I wanted trace to have all the properties of a string // // that could be manipulated with all of string's members // // but had a little more functionality that made it useful // // for debugging. trace simply derives from string. // // // // string is closed for modification, but open for extension // // - well, not completely, as it does not provide a virtual // // destructor, so we can't use std::string pointers to // // traceable's stored on heap. // // // ///////////////////////////////////////////////////////////////// #include #include #include #include /////////////////////////////////////////////////////////////// // trace class // class trace : public std::string { public: void SetStream(std::ostream* pOs) { pStream = pOs; } trace(const std::string& functionName = "test", std::ostream* pOs = &std::cout) : pStream(pOs) { static_cast(*this) = functionName; *pStream << "\n Entering " << functionName; } trace& operator=(const char* str) { static_cast(*this) = str; return *this; } template trace& Add(const T& t) { std::ostringstream oss; oss << t; *this += ' '; *this += oss.str(); return *this; } virtual ~trace() { *pStream << "\n leaving " << *this; // The sensible use of dynamic_cast, below, requires setting // \Configuration Properties\C/C++\Language\Enable Run-Time Type Info std::ofstream *pOut = dynamic_cast(pStream); if(pOut) pOut->close(); } protected: std::ostream* pStream; private: // size_t argument is the size of the allocation, passed by the compiler // void* is a pointer to a block of memory used in placement new void* operator new(size_t); void* operator new(size_t, void*); void operator delete (void*) { std::cout << "delete called" << "\n"; } void operator delete (void*, void*); void* operator new[](size_t); void* operator new[](size_t, void*); void operator delete[](void*); void operator delete[](void*, void*); }; // //----< test stub >-------------------------------------------- using namespace std; // emit newlines at end class GlobalNewLines { public: ~GlobalNewLines() { std::cout << "\n\n"; } } glob; // A global trace can serve as a repository // for data from all functions called. trace global("global"); // Need to declare ofstream here so it goes out of scope // after logger uses it in its destructor. ofstream out; trace logger("logger"); void aFunction(double d) { trace t("aFunction"); logger.Add("\n aFunction(").Add(d).Add(")"); global.Add("\n aFunction(").Add(d).Add(")"); } template std::string title(const std::string & t) { std::string underline(t.length()+2,ch); std::string temp = string("\n ") + underline + string("\n ") + t + string("\n ") + underline; return temp; } int main() { cout << title<'='>("Testing trace derived from std::string"); out.open("test.dat"); out << title<'='>("Testing trace with log file"); logger.SetStream(&out); logger.Add("\n main()"); global.Add("\n main()"); trace t("main"); t += "\n with more stuff"; t.Add(-13.5); aFunction(3.1415927); std::cout << "\n"; ///////////////////////////////////////////////// // - These operations fail to compile because // we made operators new and delete private // - This prevents incorrect behavior that // would otherwise occur if a user of trace // created an instance on the heap and // subsequently deleted, as std::string's // destructor is not virtual // // trace* pTrace = new trace("test"); // delete pTrace; // trace* pTraceArray = new trace[10]; // delete[] pTraceArray; }