2

So I have a couple classes defined thusly:

class StatLogger {
public:
  StatLogger();
 ~StatLogger();

  bool open(<parameters>);

private:
  <minutiae>
};

And a child class that descends from it to implement a null object pattern (unopened it's its own null object)

class NullStatLogger : public StatLogger {
public:
   NullStatLogger() : StatLogger() {}
};

Then I have a third class that I want to take an optional logger instance in its constructor:

class ThirdClass {
public:
  ThirdClass(StatLogger& logger=NullStatLogger());
};

My problem is when I do it as above, I get:

error: default argument for parameter of type ‘StatLogger&’ has type ‘NullStatLogger’

And if I put an explicit cast in the definition, I get:

error: no matching function for call to ‘StatLogger::StatLogger(NullStatLogger)

Complaining about not having a constructor from a NullStatLogger even though it's a child class. What am I doing wrong here, is this allowed in C++?

2
  • @luke: NullStatLogger() is not a type, but a value-initialized rvalue of type NullStatLogger Commented Jan 27, 2010 at 20:06
  • Before using the accepted solution by Jerry, please read all the available comments to that answer for a discussion on the implications of the proposed solution and other alternatives. Commented Jan 28, 2010 at 8:08

5 Answers 5

5

I you want to use inheritance and polymorphism, ThirdClass needs to use either a pointer or a reference to StatLogger object, not with an actual object. Likewise, under the circumstances you almost certainly need to make StatLogger::~StatLogger() virtual.

For example, modified as follows, the code should compile cleanly:

class StatLogger {
public:
  StatLogger();
  virtual ~StatLogger();

//  bool open(<parameters>);

private:
//  <minutiae>
};

class NullStatLogger : public StatLogger {
public:
   NullStatLogger() : StatLogger() {}
};

class ThirdClass {
    StatLogger *log;
public:
  ThirdClass(StatLogger *logger=new NullStatLogger()) : log(logger) {}
};

Edit: If you prefer a reference, the code looks something like this:

class StatLogger {
public:
  StatLogger();
  virtual ~StatLogger();

//  bool open(<parameters>);

private:
//  <minutiae>
};

class NullStatLogger : public StatLogger {
public:
   NullStatLogger() : StatLogger() {}
};

class ThirdClass {
    StatLogger &log;
public:
  ThirdClass(StatLogger &logger=*new NullStatLogger()) : log(logger) {}
};
Sign up to request clarification or add additional context in comments.

13 Comments

Just remember to delete log in ThirdClass' destructor.
@luke: this is actually rather ugly as it's written -- ThirdClass should delete log in its dtor if and (the ugly part) only if its using a logger that was allocated as a default parameter. Otherwise, you normally want allocations and deletions to be mirrored, so deleting log should be left to whoever allocated it...
Is there any way to do it with a reference? I'd prefer that over passing in a pointer if it's at all possible...
Sure enough using the new operator and dereferencing it works, why won't it work just creating a new instance of NullStatLogger?
@Jerry, quite right, new/delete should definitely be the same "person's" responsibility. The fact that this causes an here issue seems to be a good argument for refactoring this design. Maybe make the Logger parameter of ThirdClass a template "policy" parameter.
|
3

Based on the discussion in Jerry's answer, what about simplifying the problem by not using a default variable at all:

class ThirdClass
{

    StatLogger log;

    public:

        ThirdClass() : log(NullLogger()) {}
        ThirdClass(const StatLogger& logger) : log(logger) {}
};

Comments

2

There is no problem in using a derived instance as default argument for a base reference.

Now, you cannot bind a non-constant reference to a temporary (rvalue) which can be one reason for the compiler to complain about your code, but I would expect a better diagnose message (cannot bind temporary to reference or something alike).

This simple test compiles perfectly:

class base {};
class derived : public base {};
void f( base const & b = derived() ) {} // note: const &
int main() {
   f();
}

If the function must modify the received argument consider refactoring to a pointer and provide a default null value (not a default dynamically allocated object).

void f( base * b = 0) {
   if (b) b->log( "something" );
}

Only if you want to keep the non-const reference interface and at the same time provide a default instance, then you must provide an static instance, but I would recommend against this:

namespace detail {
   derived d;
   // or:
   derived & null_logger() {
      static derived log;
      return log;
   }
}
void f( base & b = detail::d ) {}
// or:
void g( base & b = detail::default_value() ) {}

Comments

1

Well for a default value I believe you have to provide a default value...

ThirdClass(StatLogger *logger = NULL)

for example

Comments

0

Uh, I know this is an oooold question, but I just had the exact same problem, and after reading all the proposed answers and comments, I came up with a slightly different solution.

I think it also might just be appropriate for the problem instance presented here, so here it goes:

Make the NullStartLogger a singleton type of object!

For me, it was quite elegant and sort. Very shortly, singleton is an object that you can not construct at will, since only and exactly one instance can exist at all time. (Alternatively, there might be 0 instances before the first usage, since you can postpone the initialization). You can of course only add the singleton functionality in to your derived class, while all the other instances (and derivations) of the parent class can be initialized and created normally. But, if NullStatLogger is, as it was in my case, just a dummy class, it does not store any data externally and does not have different behavior depending on the instance/init parameters, singleton class fits well.

Here's a short code snipped making your NullStatLogger a singleton, as well as a way to use it in the ThirdClass:

class NullStatLogger : public StatLogger {
private:
   NullStatLogger() : StatLogger() {}
   static NullStatLogger *_instance;
public:       
   static NullStatLogger &instance();
};

NullStatLogger::_instance = 0;

NullStatLogger &NullStatLogger:instance() {
    if (_instance == 0)
        _instance = new NullStatLogger(); // constructor private, this is
                                           // the only place you can call it
    return *_instance;                     // the same instance always returned
}


class ThirdClass {
public:
    ThirdClass(StatLogger& logger=NullStatLogger::instance());
};

I know this surely won't help to whomever asked the question, but hopefully it helps someone else.

2 Comments

That doesn't work if the object is read/write because the null could be changed into something else over time (although frankly it shouldn't.) But I'd be worried because ThirdClass accepts a non-const logger.
@AlexisWilke In my case, the class I used as a singleton was a dummy object with no real functionality (e.g. only function had a constant return 1 behavior), just a placeholder made for testing before implementing a more-complex functionality. It was never meant to be used by the end-user. Also, in ThirdClass(), if you try assigning anything to logger initiated with default parameter that's not a NullStatLogger, you're trying to assign a wrong class type, and the only instance of NullStatLogger accessible is the one already contained in logger, so no harm done.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.