views:

212

answers:

4

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++?

+5  A: 

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) {}
};
Jerry Coffin
Just remember to delete log in `ThirdClass`' destructor.
luke
@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...
Jerry Coffin
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...
gct
Sure enough using the new operator and dereferencing it works, why won't it work just creating a new instance of NullStatLogger?
gct
@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.
luke
@gct: it's a lifetime problem -- if you pass a `NullStatLogger` to `ThirdClass` that's local to some other function, the `NullStatLogger` will cease to exist as soon as that function exits, and `ThirdClass` will be left with a dangling reference (i.e. a reference to an object that no longer exists). There are some rules that extend the lifetime of an object used to initialize a reference, but they're not enough to save you in this case.
Jerry Coffin
@Jerry, The reference version of that example leaks unavoidably, doesn't it?
luke
@luke:No. The dtor would just do: `delete `
Jerry Coffin
So there's probably no nice way to do this without the ThirdClass having to handle deleting something in some cases. I guess I'll just have a setter for setting the StatLogger and check for a null pointer before using it.
gct
This answer is completely misleading. The problem is that you cannot bind a non-constant reference to an rvalue expression. When the hackery dereference to a new object takes place, the resulting expression is an lvalue and can be bound to the allocated object. This is a **really ugly problematic hackery** solution to a non-problem.
David Rodríguez - dribeas
@dribeas:I have to disagree. At least if his `StatLogger` is anything like you'd normally expect (i.e. basically something rather stream-like) a reference to a const object simply won't be useful at all. You could certainly make code *compile* by using a reference to const, but the result would almost certainly be useless (unless you cheated the system and made essentially everything in `StatLogger` and all its derivatives mutable, to eviscerate the `const`).
Jerry Coffin
Dynamically creating an object **in each invocation** that does not provide the argument is not a good solution. If you do need a null-object, just create one static null object and use that as a default value. Using a new dynamically allocated object as default parameter will either leak memory (if it is not deleted) or change the semantics of the function into 'I will delete whatever you pass me', which is a rather disturbing change. If you have more than one instance of `ThirdClass` and you want to use an actual logger, will you create copies or face double deletions??
David Rodríguez - dribeas
@dribeas:this is "each invocation" of a `ThirdClass` *ctor*. It means each `ThirdClass` object has exactly one associated logging object. While it's *possible* that the OP wants something else, that's a perfectly reasonable design, and entirely in keeping with what he has indicated about what he wants.
Jerry Coffin
A: 

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

ThirdClass(StatLogger *logger = NULL)

for example

Ricardo Ferreira
+1  A: 

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() ) {}
David Rodríguez - dribeas
+2  A: 

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) {}
};
luke
That would work quite nicely I think...
gct