Using a C++ child class instance as a default parameter?
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 opt开发者_JAVA百科ional 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++?
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) {}
};
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) {}
};
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() ) {}
Well for a default value I believe you have to provide a default value...
ThirdClass(StatLogger *logger = NULL)
for example
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.
精彩评论