making a cpp library: how to instantiate itself (like Serial) ?

Hi everyone, i have a little problem and my c++ skills are pretty rusty since i mostly did C for years.

I am trying to remove that rust by writing small libs in cpp, which allows me to clean some projects i have.

If that has any influence, please note i use both esp8266 and arduinos.

Right now i have been converting a basic set of logging functions into a class, with some macros to disable it when the debugging is over and we want to reduce memory footprint… but for this to work, il requires the instance to have a specific name: Logger.

And for the life of me, i cannot make it to work .

If i declare “LogManagement Logger;” in the .cpp file,with “extern LogManagement Logger;” in the header guards in the .h file i get this error:

error: ‘Logger’ was not declared in this scope

If i declare it in the .h file, i get a link error saying it was already declared (not surpising) .
If i declare it in the .h file inside the header guards and the extern directive in the other part of the header guard, i am back to the first error.

If i declare it in the .ino file, it compiles with no error, but i do not want to have it there.

My question is:
how do I Instantiate that lib class with the name Logger inside my library, so there is no need to instantiate it in the .ino file, just like we use the Serial class ? I tried to find how this one works but could not find it.

Thanks a lot.

Here is the code:

LogManagement.h

extern LogManagement Logger;

#ifndef _CGE_LOG_MANAGEMENT_H
#define _CGE_LOG_MANAGEMENT_H

#ifdef NEED_LOG
  #ifdef NEED_QUEUE_LOG
    #define LOG(x)    {do {Serial.print(x); Logger.addLog(x);)}while(0);}
    #define LOG_LN(x)  {do {Serial.println(x); Logger.addLog(x);}while(0);}
  #else
    #define LOG(x)     {Serial.print(x);}
    #define LOG_LN(x)   {Serial.println(x);}
  #endif
#else
  #define LOG(x)      {}
  #define LOG_LN(x)    {} 
#endif 



#include <queue>
#include <ostream>
#include <string>

#include "arduino.h"
#define CGE_MAX_LOG_COUNT 12

using namespace std; 
class LogManagement
{
	
  public:
  
  LogManagement(int maxLogCount= CGE_MAX_LOG_COUNT); 
  ~LogManagement(void);
  void begin(void);
  void addLog(int logToAdd);
  void addLog(string &logToAdd);
  void addLog(char * logToAdd);

  string getOneLog(void);
  string getAllLogs(bool withBrSeparator);
  
  private:
  int _maxLogCount;
  queue<string*> _logQueue;  
};

#endif

LogManagement.cpp

#include "LogManagement.h"

  LogManagement Logger;

  LogManagement::LogManagement(int maxLogCount)
  {
    if (maxLogCount > CGE_MAX_LOG_COUNT)
      maxLogCount =CGE_MAX_LOG_COUNT;

      _maxLogCount = maxLogCount;
  }
  
  LogManagement::~LogManagement(void)
  {
    while(!_logQueue.empty())
    {
      delete _logQueue.front();
      _logQueue.pop(); 
    }
  }

  string LogManagement::getOneLog(void)
  { 
	string str("");
	
    if (!_logQueue.empty())
    {
      string *pStr = _logQueue.front();
	  str.append(*pStr);
	  delete pStr;
      _logQueue.pop();
    }
	
    return str;
  } 

  string LogManagement::getAllLogs(bool withBrSeparator)
  { 
	string str("");
	
    while(!_logQueue.empty())
    {
      string *pStr = _logQueue.front();
	  str.append(*pStr);
	  if (withBrSeparator)
		  str.append("
");
	  else 
		  str.append("\n");
	  delete pStr;
      _logQueue.pop();
    }
	
    return str;
  } 


  // iostream lib too big to be included ...
  //istream &operator>>( istream  &input, LogManagement &Obj)
  //{ 
    /* check if we reached max size, and if so, cleanup */
//    while (_logQueue.size() >= _maxLogCount)
//    {
//      delete _logQueue.front();
//      _logQueue.pop();
//    }

    /* now insert the new element */
//   string *pStr = new string(input);
//   _logQueue.push(pStr);
// } 



  void LogManagement::addLog(int logToAdd)
  {
	char tmpBuffer[60];
	snprintf(tmpBuffer,59,"%d",logToAdd);
	addLog(tmpBuffer);
  }
  
  void LogManagement::addLog(char * logToAdd)
  { 
    string str(logToAdd);
    addLog(str);
  } 


  void LogManagement::addLog(string &logToAdd)
  { 
      /* check if we reached max size, and if so, cleanup */
    while (_logQueue.size() >= _maxLogCount)
    {
      delete _logQueue.front();
      _logQueue.pop();
    }

    /* now insert the new element */
    string *pStr = new string(logToAdd);
    _logQueue.push(pStr);
  } 
  
  void LogManagement::begin(void)
  {

  }

simpleExample.ino

#define NEED_LOG 1
#define NEED_QUEUE_LOG 1


#include "LogManagement.h"
#include <string>

void setup(void)
{
	Serial.begin(115200);
	Logger.begin();  
}

void loop(void)
{
	LOG_LN("test log1")
  LOG_LN("test log2")
  LOG_LN("test log3")
	string str = Logger.getOneLog();
	Serial.println(str.c_str());

  str = Logger.getAllLogs(false);
  Serial.println(str.c_str());

 

}

Put:

extern LogManagement Logger;

at the very end of the .h file. It should be outside of the class declaration, but inside the #include guards.

Thanks !

I was coming to report that i found the solution, although a slightly different one.

The "extern LogManagement Logger;" was needed at the end of the file indeed. I placed it before the class definition, hence why it did not work.

My solution was to move it at the very end, outside the header guard.

I tested your solution and it works too. Is there a particular reason i should put in inside the header guards ?

Crousti:
I tested your solution and it works too. Is there a particular reason i should put in inside the header guards ?

Yes, so it isn’t done twice if the header happens to be included twice. Same reason the guards are there in the first place.

I understand that, but i mean since it is an extern directive, i thought it would be needed every time the lib is included.

Thank you for your patience, i know this was a very simple question. I wonder how i did not see that sooner though :slight_smile:

To get rid of some warning messages:

Change ‘addLog(char * logToAdd)’ to ‘addLog(const char * logToAdd)’
Change ‘_maxLogCount’ from ‘int’ to ‘unsigned’.

Note: When I try to compile for Arduino UNO, these lines fail. You will need some conditional compilation if you want to have Arduino support.

#include <queue>
#include <ostream>
#include <string>
#include <string>

It's only needed when the rest is also needed. So inside the guards it will be there for both the include from the .cpp as when you include it in the main app. Because the main app and the .cpp are compiled independent.

But trouble starts when you include it in your application but also include another library that also includes that library. The guards only include the whole class definition once (otherwise you would try to define it twice) but same goes for the external. If it's outside the guards it will try do define it a second time.

Aka, the guards can (but should not!) be discarded for simple uses like this. It's the more complex situations where you need them. Once had a great site about it but can't find it right now :frowning:

@john thanks. For now i am mostly working with esp8266 for home automation, but i will change that to get it working on arduinos.

I am also working toward removing String from my code since it seems bad practice on MCUs .

@septillon thank you too. Inside the guard it goes then !

Avoid using queue, strings or any kind of dynamic memory, instead you can use an array of char arrays, as a circular buffer