1

I'm trying to implement a logger with 3 levels of information: general (date/time), context, message

To reach this goal I'm trying to implemnet the following pattern:

  1. Logger class (non relevant here)
  2. Context class
    • Base class LoggerContext, this has the functionality of generating the general level infos
    • Derived class, which add the context specific infos (specific for a part of the application)

The interesting part starts as I try to have a none context. That is, if the Logger is called without context, then a singleton LoggerContextNone should be used.

Here my code, that regardless of how I turn it, does not compile:

#include <string>
#include <iostream>
#include <stdexcept>

using namespace std;

enum class LoggerArea {
        LOGGER_NONE, LOGGER_DOWNLOAD, LOGGER_COMPUTE,
};

class ELoggerContext: std::runtime_error {
        using std::runtime_error::runtime_error;
};

class LoggerContextNone; // forward declaration, only needed for 
                         // the commented version of the code

class LoggerContext {
protected:
        LoggerArea mLA;
public:
        LoggerContext(LoggerArea la);
        virtual ~LoggerContext() = 0;
        /*
        static LoggerContext& getEmptyContext() {
                static LoggerContextNone loggerContextNone = { LoggerArea::LOGGER_NONE };
                return loggerContextNone;
        }
        */
        std::string getGeneral();
        virtual std::string getContext() = 0; // pure virtual
};

string LoggerContext::getGeneral() {
        return "general informations";
}
LoggerContext::LoggerContext(LoggerArea la) :
                mLA(la) {
  if (la == LoggerArea::LOGGER_NONE) {
        throw ELoggerContext("LOGGER_NONE cannot be instantiated");
  }
}

class LoggerContextNone : LoggerContext {
private:
        LoggerContextNone() {
                mLA = LoggerArea::LOGGER_NONE;
        }
public:
        virtual ~LoggerContextNone() override {

        }
        virtual std::string getContext() override {
                return " ";
        }
        static LoggerContextNone& getInstance() {
                static LoggerContextNone instance {};
                return instance;
        }
};

int main() {
  // this should not be compilable:
  LoggerContextNone n{LoggerArea::LOGGER_NONE};
  // this should at least throw an error at run time:
  LoggerContext n{LoggerArea::LOGGER_NONE};
  return 0;
}

Goals:

  • LoggerContextNone should derive from LoggerContext, because we need getGeneral()
  • LoggerContextNone should not be instantiable outside getInstance (singleton)
  • the base class should have no empty constructor, BUT the derived class should have one
  • LoggerContextNone should not call the super constructor, otherwise it would throw an error ELoggerContext
  • any derived class from LoggerContext should not be able to override getGeneral()

Is it actually possible to achieve this in C++? I'm looking for a clean solution (no factory, ...)

The compiler's error are:

19_virtual_class.cpp: In constructor ‘LoggerContextNone::LoggerContextNone()’:
19_virtual_class.cpp:45:22: error: no matching function for call to ‘LoggerContext::LoggerContext()’
  LoggerContextNone() {
                      ^
[...]
19_virtual_class.cpp: In function ‘int main()’:
19_virtual_class.cpp:62:46: error: no matching function for call to ‘LoggerContextNone::LoggerContextNone(<brace-enclosed initializer list>)’
   LoggerContextNone n{LoggerArea::LOGGER_NONE};
                                              ^

Final note: This pattern seems me conceptually simple: Many class's derived from a base class with additionally a default class.

EDIT:

If I adjust the code as by @Angew :

#include <string>
#include <iostream>
#include <stdexcept>

using namespace std;

enum class LoggerArea {
        LOGGER_NONE, LOGGER_DOWNLOAD, LOGGER_COMPUTE,
};

class ELoggerContext: std::runtime_error {
        using std::runtime_error::runtime_error;
};

class LoggerContextNone;

class LoggerContext {
protected:
        LoggerArea mLA;

        class LoggerContextNone_AccessToken
        {
                friend LoggerContextNone;
                LoggerContextNone_AccessToken() {}
        };
        explicit LoggerContext(LoggerContextNone_AccessToken) : mLA(LoggerArea::LOGGER_NONE) {}
public:
        LoggerContext(LoggerArea la);
        virtual ~LoggerContext() = 0;
        std::string getGeneral();
        virtual std::string getContext() = 0;
};

string LoggerContext::getGeneral() {
        string s = "general informations:";
        if (mLA==LoggerArea::LOGGER_NONE) { s += "LOGGER_NONE"; }
        else if (mLA==LoggerArea::LOGGER_DOWNLOAD) { s += "LOGGER_DOWNLOAD"; }
        else if (mLA==LoggerArea::LOGGER_COMPUTE) { s += "LOGGER_COMPUTE"; }
        else { s += "??????????"; }

        return s;
}
LoggerContext::LoggerContext(LoggerArea la) :
                mLA(la) {
  if (la == LoggerArea::LOGGER_NONE) {
        throw ELoggerContext("LOGGER_NONE cannot be instantiated");
  }
}

class LoggerContextNone : LoggerContext {
private:
        LoggerContextNone(): LoggerContext(LoggerContextNone_AccessToken()) {}
public:
        virtual ~LoggerContextNone() override {

        }
        virtual std::string getContext() override {
                return " ";
        }
        static LoggerContextNone& getInstance() {
                static LoggerContextNone instance {};
                return instance;
        }
};

class LoggerContextDerived : LoggerContext {
public:
        virtual std::string getContext() override {
                return "derived context";
        }
};

int main() {
  LoggerContextDerived n {LoggerArea::LOGGER_DOWNLOAD};

  // cout << "General : " << n.getGeneral() << endl;
  // cout << "Context : " << n.getContext() << endl;

  return 0;
}

I cannot instantiate a derived class. g++ says:

9_virtual_class.cpp: In function ‘int main()’:
19_virtual_class.cpp:78:54: error: no matching function for call to ‘LoggerContextDerived::LoggerContextDerived(<brace-enclosed initializer list>)’
   LoggerContextDerived n {LoggerArea::LOGGER_DOWNLOAD};
                                                      ^

And it suggest me to use the copy constructor or the move constructor. That means to me that the constructor

LoggerContext(LoggerArea la);

is not visible in the derived class.

PeptideChain
  • 543
  • 3
  • 16

2 Answers2

3

You can achieve the result you want, but not quite in the way you've tried. The problematic requirement is this one:

LoggerContextNone should not call the super constructor, otherwise it would throw an error ELoggerContext

A derived class will always call a constructor of the base class. In C++, you cannot legally have a valid object of a class type without having run its constructor.

However, note that it will call a constructor of the base, meaning it can call an arbitrary one (derived class decides). So you could give the base class a constructor specifically intended for use by LoggerContextNone, something like this:

class LoggerContext {
protected:
        LoggerArea mLA;
        LoggerContext() : mLA(LOGGER_NONE) {}
public:
        LoggerContext(LoggerArea la);
        virtual ~LoggerContext() = 0;
        /*
        static LoggerContext& getEmptyContext() {
                static LoggerContextNone loggerContextNone = { LoggerArea::LOGGER_NONE };
                return loggerContextNone;
        }
        */
        std::string getGeneral();
        virtual std::string getContext() = 0; // pure virtual
};

This will achieve what you want, but it will allow all classes derived from LoggerContext to call that default constructor, should they choose to do so. If you want to avoid that and only make that constructor available to LoggerContextNone, you can use friendship tricks and tag dispatch to do so:

class LoggerContext
{
protected:
  class LoggerContextNone_AccessToken
  {
    friend LoggerContextNone;
    LoggerContextNone_AccessToken() {}
  };

  explicit LoggerContext(LoggerContextNone_AccessToken) : mLA(LOGGER_NONE) {}
protected:
  // ... the rest as before
};

LoggerContextNone::LoggerContextNone() : LoggerContext(LoggerContextNone_AccessToken())
{}

This means that:

  1. To call the non-throwing constructor of LoggerContext, you need to pass in a LoggerContextNone_AccessToken object.

  2. LoggerContextNone_AccessToken has a private constructor, which means only its friends can construct it.

  3. LoggerContextNone is the only friend of LoggerContextNone_AccessToken, so it is the only class capable of constructing a LoggerContextNone_AccessToken and therefore the only class capable of calling the non-throwing constructor of LoggerContext.


Alternatively, you could consider whether you actually need LoggerContextNone at all. Perhaps you could have LoggerContext behave as LoggerContextNone, and just allow derived classes to provide non-none behaviour.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • thank you for your long reply, but it seams to be that something is still not working with the constructor, see my edit of the question. – PeptideChain May 17 '16 at 09:06
  • 1
    @LiPo That's a rather unrelated issue. You cannot use a base-class ctor to construct a derived class in C++. If you want a single-param ctor in the derived class, you need to define such a ctor there. Or use constructor inheritance (`using LoggerContext::LoggerContext`), but that will inherit *all* constructors. – Angew is no longer proud of SO May 17 '16 at 09:25
  • 1
    @LiPo You might want to read up more on how C++ constructors work in a [good book](http://stackoverflow.com/q/388242/1782465). – Angew is no longer proud of SO May 17 '16 at 09:26
0

The answer from @Angew is essential to make the pattern work, but there were many (and still there are some) other issues in the code.

This is best I could make out of it, it is still not working, but perhaps in the next days I get it 100%:

#include <string>
#include <iostream>
#include <stdexcept>

using namespace std;

enum class LoggerArea {
        LOGGER_NONE, LOGGER_DOWNLOAD, LOGGER_COMPUTE,
};

class ELoggerContext: std::runtime_error {
        using std::runtime_error::runtime_error;
};

class LoggerContextNone;

class LoggerContext {
protected:
        LoggerArea mLA;

        class LoggerContextNone_AccessToken
        {
                friend LoggerContextNone;
                LoggerContextNone_AccessToken() {}
        };
        explicit LoggerContext(LoggerContextNone_AccessToken) : mLA(LoggerArea::LOGGER_NONE) {}
public:
        LoggerContext(LoggerArea la);
        virtual ~LoggerContext() {};
        std::string getGeneral();
        virtual std::string getContext() = 0;
};

string LoggerContext::getGeneral() {
        string s = "general informations:";
        if (mLA==LoggerArea::LOGGER_NONE) { s += "LOGGER_NONE"; }
        else if (mLA==LoggerArea::LOGGER_DOWNLOAD) { s += "LOGGER_DOWNLOAD"; }
        else if (mLA==LoggerArea::LOGGER_COMPUTE) { s += "LOGGER_COMPUTE"; }
        else { s += "??????????"; }

        return s;
}
LoggerContext::LoggerContext(LoggerArea la) :
                mLA(la) {
  if (la == LoggerArea::LOGGER_NONE) {
        throw ELoggerContext("LOGGER_NONE cannot be instantiated");
  }
}

class LoggerContextNone : LoggerContext {
private:
        LoggerContextNone(): LoggerContext(LoggerContextNone_AccessToken()) {}
public:
        virtual ~LoggerContextNone() override {

        }
        virtual std::string getContext() override {
                return " ";
        }
        static LoggerContextNone* getInstance() {
        // this was:
        // static LoggerContextNone& getInstance() {
                static LoggerContextNone instance {};
                return &instance;
        }
};

class LoggerContextDerived : public LoggerContext {
public:
        LoggerContextDerived(LoggerArea la):LoggerContext(la) {  };
        virtual std::string getContext() override {
                return "derived context";
        }
        virtual ~LoggerContextDerived() override {

        }
};

int main() {

  // test 1: derived class
  LoggerContextDerived c1 {LoggerArea::LOGGER_DOWNLOAD};   // ok

  cout << "General : " << c1.getGeneral() << endl;         // ok
  cout << "Context : " << c1.getContext() << endl;         // ok

  LoggerContext * c2 = &c1;                                // ok

  // test 2: derived none class
  LoggerContextNone * c3 = LoggerContextNone::getInstance();   // ok
  LoggerContext * c4 = c3;                                     // g++ error:
    // error: ‘LoggerContext’ is an inaccessible base of ‘LoggerContextNone’
  LoggerContext * c5 = LoggerContextNone::getInstance();       // g++ error:
    // error: ‘LoggerContext’ is an inaccessible base of ‘LoggerContextNone’

  return 0;
}
PeptideChain
  • 543
  • 3
  • 16