В С++ существует ли консенсус относительно поведения по умолчанию против явного кода?

Мне интересно, есть ли какая-либо причина явно писать код, который делает то же самое, что и поведение С++ по умолчанию.

Вот код:

class BaseClass
{
public:
    virtual ~BaseClass() {}

    virtual void f() { /* do something */ }
};

class ExplicitClass
    : public BaseClass
{
public:
    ExplicitClass()
        : BaseClass()   // <-- explicit call of base class constructor
    {
        // empty function
    }

    virtual ~ExplicitClass() {}  // <-- explicit empty virtual destructor

    virtual void f() { BaseClass::f(); }  // <-- function just calls base
};

class ImplicitClass
    : public BaseClass
{    
};

Мне в основном любопытно в сфере рефакторинга и изменения кодовой базы. Я не думаю, что многие программисты намереваются написать такой код, но он может выглядеть так, как будто код меняется со временем.

Есть ли смысл оставлять код, присутствующий в ExplicitClass? Я вижу бонус, что он показывает вам, что происходит, но он встречается как склонный к риску и рискованный.

Лично я предпочитаю удалить любой код, который является кодом поведения по умолчанию (например, ImplicitClass).

Есть ли какой-либо консенсус в пользу того или другого?

Ответ 1

Существует два подхода к этой проблеме:

  • Определите все, даже если компилятор генерирует то же самое,
  • Не определяет ничего, что скомпилирует, будет лучше.

Верующие из (1) используют такие правила, как: "Всегда определять C-tor по умолчанию, Копировать C-тор, оператор присваивания и d-tor".

(1) верующие считают, что безопаснее иметь больше, чем что-то пропустить. К сожалению (1) особенно любят наши менеджеры - они считают, что лучше иметь, чем не иметь. Sp такие правила, как это, "всегда определяют большую четверку", переходят к "Стандарту кодирования" и должны соблюдаться.

Я верю в (2). И для фирм, где такие стандарты кодирования существуют, я всегда ставил комментарий "Не определяйте копирование c-tor, как это делает компилятор"

Ответ 2

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

В соответствии с вашим вопросом, есть ли причина для его написания, нет, поскольку явный и неявный класс ведут себя одинаково. Люди иногда делают это по причинам "обслуживания", например. если производный f когда-либо реализован по-другому, чтобы не забыть вызвать базовый класс. Я лично, не считаю это полезным.

Ответ 3

В любом случае все в порядке, если вы понимаете, что на самом деле происходит там, и проблемы, которые могут возникнуть в результате не написания функций самостоятельно.

ИСКЛЮЧЕНИЕ-БЕЗОПАСНОСТЬ:

Компилятор будет генерировать функции, неявно добавляющие необходимые условия throw. Для неявно созданного конструктора это будет каждое условие броска от базовых классов и членов.

ILL-FORMED CODE

Есть несколько сложных случаев, когда некоторые из автоматически сгенерированных функций членов будут плохо сформированы. Вот пример:

class Derived;

class Base
{
public:
  virtual Base& /* or Derived& */
  operator=( const Derived& ) throw( B1 );

  virtual ~Base() throw( B2 );
};

class Member
{
public:
  Member& operator=( const Member& ) throw( M1 );
  ~Member() throw( M2 );
};

class Derived : public Base
{
  Member m_;
  //   Derived& Derived::operator=( const Derived& )
  //            throw( B1, M1 ); // error, ill-formed
  //   Derived::~Derived()
  //            throw( B2, M2 ); // error, ill-formed
};

operator= плохо сформирован, потому что его директива throw должна быть по крайней мере столь же ограничительной, как и ее базовый класс, что означает, что он должен бросать либо B1, либо вообще ничего. Это имеет смысл, поскольку объект Derived также можно рассматривать как базовый объект.

Обратите внимание, что совершенно законно иметь плохо сформированную функцию, если вы ее никогда не вызываете.

В основном я переписываю GotW # 69, поэтому, если вы хотите получить более подробную информацию, вы можете найти их здесь

Ответ 4

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

class ExplicitClass
    : public BaseClass
{
public:

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

    ExplicitClass()
        : BaseClass()   // <-- explicit call of base class constructor
    {
        // empty function
    }

пустой виртуальный деструктор - это IME, статистически лучшее место для экспорта virtual (конечно, это определение будет в другом месте, если оно видимо более чем на один перевод). вы хотите, чтобы это экспортировалось, потому что есть тонна информации rtti и vtable, которая может оказаться ненужной раздутой в вашем двоичном файле. я фактически определяю пустые деструкторы очень часто по этой причине:

    virtual ~ExplicitClass() {}  // <-- explicit empty virtual destructor

возможно, это соглашение в вашей группе, или оно документирует, что это именно то, что предназначена для реализации. это также может быть полезным (субъективным) в больших кодовых версиях или в сложных иерархиях, поскольку оно также может помочь вам напомнить о динамическом интерфейсе, который, как ожидается, будет принят. некоторые люди предпочитают все объявления в подклассе, потому что они могут видеть всю динамическую реализацию класса в одном месте. поэтому местность помогает им в том случае, если иерархия/интерфейс класса больше, чем ментальный стек программиста. как деструктор, этот виртуальный может также быть хорошим местом для экспорта typeinfo:

    virtual void f() { BaseClass::f(); }  // <-- function just calls base
};

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

конечная причина (которая качается в обоих направлениях) заключается в том, что явные определения по умолчанию могут увеличивать и уменьшать время сборки и ссылки.

К счастью, теперь проще и недвусмысленно указать стандартные и удаленные методы и конструкторы.