Domanda

I want to log messages from both a base class B and a derived class C, being distinct about which class has logged the message:

#include "iostream"

class Logger {
public:
    Logger(std::string name) : name_(name) {}
    void log(std::string msg) { std::cout << name_ << ": " << msg << std::endl; }
private:
    std::string name_;
};

class B : public Logger {
public:
    B() : Logger("Class B" ) {}
    void doSomethingInB() {
        log("B doing something");
    }
};

class C : public B, public Logger {
public:
    C() : Logger("Class C" ) {}
    void doSomethingInC() {
        log("C doing something");
    }
};

int main() {
    B* b = new B();
    C* c = new C();

    b->doSomethingInB(); // I want this to output: Class B: B doing something

    c->doSomethingInC();
    c->doSomethingInB(); // I also want this to output: Class B: B doing something

    return 0;
}

I get the error "direct base ‘Logger’ inaccessible in ‘C’ due to ambiguity [enabled by default]" which is self-explanatory.

What different model can I implement to do this?

È stato utile?

Soluzione 2

You can use virtual inheritance like shown in the answer of Sam Cristall, but that will make only one Logger object in an instance of C, with the side effect that the log will not output the correct name (you also found this problem in a comment of this answer).

Composition

One solution would be to use composition instead of inheritance:

class B
{
private:
    Logger log;
// ...
};

Probably the simplest solution. You can see a live example here.

Inheritance

Another solution is to make the base classes different, here by using CRTP. That is:

template<typename T>
class LoggerImp : public Logger
{
public:
    LoggerImp(const std::string& n) : Logger(n) {}
};

class B : private LoggerImp<B>
{
private:
    typedef LoggerImp<B> LL;

public:
    B() : LoggerImp<B>("Class B" ) {}

    void doSomethingInB()
    {
        LL::log("B doing something");
    }
};

class C : public B, private LoggerImp<C>
{
private:
    typedef LoggerImp<C> LL;

public:
    C() : LoggerImp<C>("Class C" ) {}

    void doSomethingInC()
    {
        LL::log("C doing something");
    }
};

Some notes:

  • I used private inheritance because as I understand your problem the logger is useful only to B or C, not owner of these objects.
  • The CRTP is used to disambiguate the base names. It is not sufficient but help a lot in your problem
  • The typedef is for readability. The fact that the logger bases have different names allows to correctly typedef them, permitting us to call the correct log function.
  • A typedef in a derived class hides a typedef in the base class (provided they're for the same name). So you can use the same code for logging in different classes.
  • You can also make the Logger class directly a CRTP.
  • You can see a live example here.

Altri suggerimenti

EDIT: In response to your requirement of c->doSomethingInB(); needing to print Class B: B doing something, consider composition over inheritance, since you need B to act as its own subclass, its logger is really not the same as C's (unless there are more requirements at play here). Example:

class Logger {
public:
    Logger(std::string name) : name_(name) {}
    void log(std::string msg) { std::cout << name_ << ": " << msg << std::endl; }
private:
    std::string name_;
};

class B {
public:
    B() : logger_("Class B" ) {}
    void doSomethingInB() {
        logger_.log("B doing something");
    }
private:
  Logger logger_;
};

class C : public B {
public:
    C() : logger_("Class C" ) {}
    void doSomethingInC() {
        logger_.log("C doing something");
    }
private:
  Logger logger_;
};
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top