6
\$\begingroup\$

I will be adding functionality and most likely additional refactoring, however a stringent review would be welcome before I build it further. I know documentation is probably a bit sparse but, you know how it is. If you feel anything significantly warrants documentation, let me know. I know I understand the code; I don't know about others.

Please review code correctness, best practices, design and code formatting.

//File      debug.h
//Release   1.2.0.0
//Copyright Michael Mercer

/* Include code
#ifdef NDEBUG
    #include <debug.h>
#else
    #undef OBJECT_NAME
    #define OBJECT_NAME debugDefault    //Name object by file or project
    #include <debug.h>
    using namespace debug_1_2_0;        //Required to debug main() and NDEBUG ForwardCall
    debug OBJECT_NAME;                  //debug OBJECT_NAME(std::initializer_list<std::ostream*>); to set ostreams
#endif

DEBUG_MAIN  //Wrap main with debug diagnostics
{
    //DEBUG(Args...);
    //DEBUG_FUNC("Function name", function, function args...);
    //DEBUG_FUNC(function, function args...);
    return 0;
}
*/

#ifdef NDEBUG

    #undef DEBUG
    #undef DEBUG_FUNC
    #undef DEBUG_MAIN
    #define DEBUG(...)
    #define DEBUG_FUNC  ForwardCall
    #define DEBUG_MAIN  int main(int argc, char** argv, char** envp)

#else

    #undef DEBUG
    #undef DEBUG_FUNC
    #undef DEBUG_MAIN
    #define DEBUG       OBJECT_NAME.debugOutput
    #define DEBUG_FUNC  DEBUG

    #define DEBUG_MAIN                                  \
    int main(int argc, char** argv, char** envp)        \
    {                                                   \
        OBJECT_NAME.debugDiagnostics(argc, argv, envp); \
    }                                                   \
    int debug::main(int argc, char** argv, char** envp)

#endif

#ifndef DEBUG_H //Include guard
#define DEBUG_H

#include <exception>    //for std::exception
#include <iostream>     //for std::ostream
#include <stdexcept>    //for std::logic_error
#include <windows.h>    //for GetStdHandle etc
#include <forward_list> //for std::forward_list
namespace debug_1_2_0
{   

    //Forward calls when NDEBUG
    template<typename... T, typename... Args>
    void ForwardCall(void funcToTry(T... Tp), Args&&... parameters)
    {
        (funcToTry)(static_cast<Args&&>(parameters)...);
    }

    //Forward calls when NDEBUG
    template<typename F, typename... T, typename... Args>
    auto ForwardCall(F funcToTry(T... Tp), Args&&... parameters) -> F
    {
        return (funcToTry)(static_cast<Args&&>(parameters)...);
    }

    //Forward calls when NDEBUG 
    template<typename... T, typename... Args>
    void ForwardCall(const char* const functionName,void funcToTry(T... Tp), Args&&... parameters)
    {
        (funcToTry)(static_cast<Args&&>(parameters)...);
    }

    //Forward calls when NDEBUG     
    template<typename F, typename... T, typename... Args>
    auto ForwardCall(const char* const functionName,F funcToTry(T... Tp), Args&&... parameters) -> F
    {
        return (funcToTry)(static_cast<Args&&>(parameters)...);
    }

#ifndef NDEBUG

    enum struct debugState {ENTER, EXIT, PROCESS};

    //Retreive file size
    __int64 FileSize(std::string name);

    class oStreams
    {
    private:
        std::forward_list<std::ostream*> listStreams;

    public:
        oStreams(std::initializer_list<std::ostream*> initOutStreams);

        typedef std::ostream& (manip)(std::ostream&);
        void operator<<(manip& m);

        template <typename T>
        void operator<<(T&& obj)
        {
            for (auto& x: listStreams)
                (*x) << static_cast<T&&>(obj);
        }
    };

    class debug
    {
    private:
        int debugIndentCount=0;
        const char* const enterRoutine = "Enter ";
        const char* const exitRoutine =  "Exit  ";
        oStreams debugStreams;

        //Output last remaining element of args...
        template <typename T>
        void logText(T&& firstOut)
        {
            debugStreams << static_cast<T&&>(firstOut);
        }

        //Output first element of args..., call recursively
        template <typename T, typename... Args>
        void logText(T&& firstOut, Args&&... remainder)
        {
            debugStreams << static_cast<T&&>(firstOut);
            logText(static_cast<Args&&>(remainder)...);
        }

        //Format and output args... to ostream
        template <typename... Args>
        void indentX(Args&&... debugText)
        {
            SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED);
            debugStreams << "# ";
            for(int x = 0; x < debugIndentCount; ++x)
                debugStreams << "   ";
            logText(static_cast<Args&&>(debugText)...);
            debugStreams << std::endl;
            SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE);
        }

        //Output custom state and args...
        template <typename... Args>
        void debugOutput(debugState state, Args&&... debugText)
        {
            switch(state)
            {
            case debugState::ENTER:
                indentX(enterRoutine, static_cast<Args&&>(debugText)...);
                debugIndentCount++;
                break;
            case debugState::EXIT:
                if (debugIndentCount>0)
                    debugIndentCount--;
                indentX(exitRoutine, static_cast<Args&&>(debugText)...);
                break;
            case debugState::PROCESS:
                indentX(static_cast<Args&&>(debugText)...);
                break;
            default:
                throw std::logic_error("Debugging error: Incorrect enum debugState");
            }
        }

    public:

        debug(std::initializer_list<std::ostream*> oStreams = {&std::clog})
            : debugStreams(oStreams)
            {};

        ~debug(){};

        //Output args...
        template <typename... Args>
        void debugOutput(Args&&... debugText)
        {
            debugOutput(debugState::PROCESS,static_cast<Args&&>(debugText)...);
        }

        //Call funcToTry with args..., catch exception      
        template<typename... T, typename... Args>
        void debugOutput(void funcToTry(T... Tp), Args&&... parameters)
        {
            try
            {
                (funcToTry)(static_cast<Args&&>(parameters)...);
            }
            catch(std::exception& e)
            {
                debugOutput("EXCEPTION THROWN! ", e.what());
                throw;
            }
        }

        //Call funcToTry with args... and return F, catch exception
        template<typename F, typename... T, typename... Args>
        auto debugOutput(F funcToTry(T... Tp), Args&&... parameters) -> F
        {
            try
            {
                return (funcToTry)(static_cast<Args&&>(parameters)...);
            }
            catch(std::exception& e)
            {
                debugOutput("EXCEPTION THROWN! ", e.what());
                throw;
            }
        }

        //Output function name
        //Call void funcToTry with args..., catch exception     
        template<typename... T, typename... Args>
        void debugOutput(const char* const functionName,void funcToTry(T... Tp), Args&&... parameters)
        {
            try
            {
                debugOutput(debugState::ENTER,functionName);
                (funcToTry)(static_cast<Args&&>(parameters)...);
                debugOutput(debugState::EXIT,functionName);
            }
            catch(std::exception& e)
            {
                debugOutput("EXCEPTION THROWN! ", e.what());
                throw;
            }
        }

        //Output function name
        //Call funcToTry with args... and return F, catch exception         
        template<typename F, typename... T, typename... Args>
        auto debugOutput(const char* const functionName,F funcToTry(T... Tp), Args&&... parameters) -> F
        {
            try
            {
                debugOutput(debugState::ENTER,functionName);
                F returnValue = (funcToTry)(static_cast<Args&&>(parameters)...);
                debugOutput(debugState::EXIT,functionName," with ", returnValue);
                return returnValue;
            }
            catch(std::exception& e)
            {
                debugOutput("EXCEPTION THROWN! ", e.what());
                throw;
            }
        }

        int main(int argc, char** argv, char** envp);

        void debugDiagnostics(int argc, char** argv, char** envp)
        {
            SYSTEMTIME stStart, stEnd;
            FILETIME ftStart,ftEnd;

            debugOutput(debugState::ENTER,"Debug Diagnostics");
            double fileSize = FileSize(argv[0]);
            debugOutput("file size in bytes = ",fileSize);
            debugOutput("file size in kilobytes = ",fileSize/1024);
            debugOutput(debugState::EXIT,"Debug Diagnostics");

            GetSystemTime(&stStart);
            debugOutput(debugState::ENTER,"Main");
            this->main(argc, argv, envp);
            debugOutput(debugState::EXIT,"Main");
            GetSystemTime(&stEnd);

            SystemTimeToFileTime (&stStart,&ftStart);
            SystemTimeToFileTime (&stEnd,&ftEnd);

            unsigned __int64 timeStart=ftStart.dwLowDateTime;
            unsigned __int64 timeEnd=ftEnd.dwLowDateTime;

            debugOutput(debugState::ENTER,"Debug Diagnostics");
            debugOutput("execution time is ", (timeEnd-timeStart)/10000000, " seconds");
            debugOutput("execution time is ", (timeEnd-timeStart)/10000, " milliseconds");
            debugOutput(debugState::EXIT,"Debug Diagnostics");
        }

    };  //class

#endif  //#ifndef NDEBUG

}       //namespace

#endif //DEBUG_H
\$\endgroup\$
3
+50
\$\begingroup\$

I didn't quite get the whole picture of your code without a usage example, but there are a few aspects I can comment on. In order of appearance:

  • main() with the extra envp parameter is not standard, though that format is accepted by the main compilers out there. Your code has a few Windows-specific portions, so I don't think portability is your concern anyway. You can read more about the signature of main in this SO question.

  • namespace debug_1_2_0: It doesn't strike me as a good idea encoding the version number of a library in the names of things. When the version changes, what will you do?

  • What you are trying to achieve with the several instances of static_cast<T &&>() through the code is what std::forward does. You should use the latter since it is a clearer and standard way of doing it.

  • Consider replacing the Visual Studio specific __int64 with the standard std::int64_t, found in the <cstdint> header. There is no advantage in using a compiler extension when a standard version of the same feature is also available.

  • These two constants:

    const char* const enterRoutine = "Enter ";
    const char* const exitRoutine =  "Exit  ";
    

    Could be static, to ensure no data is duplicated if more than one class instance is created.

  • Empty destructor in ~debug(){}; is really not necessary.

  • Minor point, but you are using a somewhat unusual naming convention for your class types. camelCase methods are usually combined with PascalCase class names, so I would expect your classes to be Debug and OStreams.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.