Использование экземпляра дочернего класса C ++ в качестве параметра по умолчанию? - PullRequest
2 голосов
/ 27 января 2010

Итак, у меня есть пара классов, определенных таким образом:

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 &’ имеет тип «NullStatLogger»

И если я добавлю явное приведение в определение, я получу:

ошибка: нет подходящей функции для вызова в «StatLogger :: StatLogger (NullStatLogger)

Жалуется на отсутствие конструктора из NullStatLogger, даже если это дочерний класс. Что я здесь не так делаю, это разрешено в C ++?

Ответы [ 5 ]

5 голосов
/ 27 января 2010

Если вы хотите использовать наследование и полиморфизм, 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) {}
};
3 голосов
/ 27 января 2010

Основываясь на обсуждении в Ответ Джерри , как насчет упрощения проблемы, вообще не используя переменную по умолчанию:

class ThirdClass
{

    StatLogger log;

    public:

        ThirdClass() : log(NullLogger()) {}
        ThirdClass(const StatLogger& logger) : log(logger) {}
};
2 голосов
/ 27 января 2010

Нет проблем в использовании производного экземпляра в качестве аргумента по умолчанию для базовой ссылки.

Теперь вы не можете привязать непостоянную ссылку к временному (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" );
}

Только если вы хотите сохранить неконстантный ссылочный интерфейс и в то же времяпредоставьте экземпляр по умолчанию, тогда вы должны предоставить статический экземпляр, но я бы рекомендовал против этого:

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() ) {}
1 голос
/ 27 января 2010

Ну, для значения по умолчанию, я думаю, вы должны предоставить значение по умолчанию ...

ThirdClass(StatLogger *logger = NULL)

например

0 голосов
/ 19 марта 2012

Э-э, я знаю, что это оооочищенный вопрос, но у меня была точно такая же проблема, и после прочтения всех предложенных ответов и комментариев я нашел немного другое решение.

Я думаю, что это также может быть подходящим для представленного здесь экземпляра проблемы, так что вот так:

Сделайте NullStartLogger типом объекта singleton !

Для меня это было довольно элегантно и вроде. Вкратце, синглтон - это объект, который вы не можете построить по своему желанию, поскольку только и только один экземпляр может существовать в любое время. (Как вариант, перед первым использованием может быть 0 экземпляров, так как вы можете отложить инициализацию). Конечно, вы можете добавить только одноэлементную функциональность в ваш производный класс, в то время как все другие экземпляры (и производные) родительского класса могут быть инициализированы и созданы нормально. Но если NullStatLogger, как это было в моем случае, является просто фиктивным классом, он не хранит никаких данных извне и не ведет себя по-разному в зависимости от параметров экземпляра / инициализации, синглтон-класс подходит хорошо.

Вот короткий код, который делает ваш 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());
};

Я знаю, что это точно не поможет тому, кто задал вопрос, но, надеюсь, это поможет кому-то еще.

...