使用C ++子类实例作为默认参数?

时间:2010-01-27 17:30:05

标签: c++ inheritance parameters default

所以我这样定义了几个类:

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

  bool open(<parameters>);

private:
  <minutiae>
};

一个子类从它下降来实现一个空对象模式(未打开它是它自己的空对象)

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

然后我有第三个类,我想在其构造函数中使用可选的logger实例:

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

我的问题是,当我按上述方式执行时,我得到:

  

错误:参数的默认参数   'StatLogger&amp;'类型有类型   “NullStatLogger”

如果我在定义中加上一个明确的强制转换,我会得到:

  

错误:没有匹配的呼叫功能   至   “StatLogger :: StatLogger(NullStatLogger)

抱怨NullStatLogger中没有构造函数,即使它是子类。我在这里做错了什么,这在C ++中是允许的吗?

5 个答案:

答案 0 :(得分:5)

我想使用继承和多态,ThirdClass需要使用指针或对StatLogger对象的引用,而不是实际对象。同样,在这种情况下,您几乎肯定需要StatLogger::~StatLogger()虚拟。

例如,修改如下,代码应该干净地编译:

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) {}
};

编辑:如果您更喜欢参考,代码如下所示:

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) {}
};

答案 1 :(得分:3)

根据Jerry's answer中的讨论,如何通过不使用默认变量来简化问题:

class ThirdClass
{

    StatLogger log;

    public:

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

答案 2 :(得分:2)

使用派生实例作为基准引用的默认参数没有问题。

现在,你不能将非常量引用绑定到临时(rvalue),这可能是编译器抱怨你的代码的一个原因,但我希望有一个更好的诊断消息(不能临时绑定引用或类似的东西) )。

这个简单的测试完美编译:

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

如果函数必须修改接收的参数,请考虑重构指针并提供默认的空值(不是默认的动态分配对象)。

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

只有当你想保留非const引用接口并同时提供默认实例时,你必须提供一个静态实例,但我建议不要这样做:

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() ) {}

答案 3 :(得分:1)

对于默认值,我相信您必须提供默认值...

ThirdClass(StatLogger *logger = NULL)

例如

答案 4 :(得分:0)

呃,我知道这是一个愚蠢的问题,但我遇到了同样的问题,在阅读了所有提出的答案和评论之后,我提出了一个稍微不同的解决方案。

我认为它也可能适用于此处提供的问题实例,所以在这里:

使NullStartLogger成为singleton类型的对象!

对我而言,它非常优雅和整洁。很快,单例是一个你无法随意构造的对象,因为只有一个实例可以随时存在。 (或者,在第一次使用之前可能有0个实例,因为您可以推迟初始化)。您当然只能将单例功能添加到派生类中,而父类的所有其他实例(和派生)都可以正常初始化和创建。但是,如果NullStatLogger在我的情况下只是一个虚拟类,它不会在外部存储任何数据,并且根据实例/ init参数不具有不同的行为,单身人士很适合。

这是一段简短的代码剪辑,让您的NullStatLogger成为单身人士,以及在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());
};

我知道这肯定对任何提出这个问题的人都没有帮助,但希望它可以帮助其他人。