Параметры по умолчанию с конструкторами С++

Хорошо ли иметь конструктор классов, который использует параметры по умолчанию, или использовать отдельные перегруженные конструкторы? Например:

// Use this...
class foo  
{
private:
    std::string name_;
    unsigned int age_;
public:
    foo(const std::string& name = "", const unsigned int age = 0) :
        name_(name),
        age_(age)
    {
        ...
    }
};

// Or this?
class foo  
{
private:
    std::string name_;
    unsigned int age_;
public:
    foo() :
    name_(""),
    age_(0)
{
}

foo(const std::string& name, const unsigned int age) :
        name_(name),
        age_(age)
    {
        ...
    }
};

Любая версия, похоже, работает, например:

foo f1;
foo f2("Name", 30);

Какой стиль вы предпочитаете или рекомендуете и почему?

Ответ 1

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

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

Ответ 2

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

Тем не менее, есть некоторые gotchas с аргументами по умолчанию, в том числе тот факт, что константы могут быть встроены (и тем самым стать частью двоичного интерфейса вашего класса). Другой, о котором следует помнить, заключается в том, что добавление аргументов по умолчанию может превратить явный конструктор с несколькими аргументами в неявный конструктор с одним аргументом:

class Vehicle {
public:
  Vehicle(int wheels, std::string name = "Mini");
};

Vehicle x = 5;  // this compiles just fine... did you really want it to?

Ответ 3

По моему опыту, параметры по умолчанию кажутся классными в то время и делают мой коэффициент лени счастливым, но потом по дороге, в которой я использую класс, и я удивляюсь, когда дефолт срабатывает. Поэтому я действительно не думаю, что это хорошая идея; лучше иметь классName:: className(), а затем классName:: init (arglist). Только для этого края ремонтопригодности.

Ответ 4

Это обсуждение применимо как к конструкторам, так и к методам и функциям.

Использование параметров по умолчанию?

Хорошо, что вам не нужно перегружать конструкторы/методы/функции для каждого случая:

// Header
void doSomething(int i = 25) ;

// Source
void doSomething(int i)
{
   // Do something with i
}

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

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

с использованием перегруженных конструкторов/методов/функций?

Хорошо, что если ваши функции не встроены, вы затем контролируете значение по умолчанию в источнике, выбирая, как будет работать одна функция. Например:

// Header
void doSomething() ;
void doSomething(int i) ;

// Source

void doSomething()
{
   doSomething(25) ;
}

void doSomething(int i)
{
   // Do something with i
}

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

Ответ 5

Ответ Сэма указывает на то, что аргументы по умолчанию предпочтительны для конструкторов, а не для перегрузки. Я просто хочу добавить, что С++ - 0x позволит делегирование от одного конструктора к другому, тем самым устраняя необходимость по умолчанию.

Ответ 6

Если создание конструкторов с аргументами является плохим (как многие утверждают), то сделать их с аргументами по умолчанию еще хуже. Недавно я пришел к выводу, что аргументы ctor плохи, потому что ваша логика ctor должна быть как можно меньше. Как вы справляетесь с обработкой ошибок в ctor, должен ли кто-нибудь передать аргумент, который не имеет никакого смысла? Вы можете либо выбросить исключение, что плохая новость, если все ваши абоненты не готовы переносить любые "новые" вызовы внутри блоков try или устанавливать некоторую "переменную-инициализацию", которая является своего рода грязным взломом.

Следовательно, единственный способ убедиться, что аргументы, переданные на этапе инициализации вашего объекта, - это создать отдельный метод initialize(), где вы можете проверить код возврата.

Использование аргументов по умолчанию плохо по двум причинам; прежде всего, если вы хотите добавить еще один аргумент в ctor, то вы застряли, положив его в начале и изменив весь API. Более того, большинство программистов привыкли находить API так, как оно используется на практике - это особенно верно для непубличного API, используемого внутри организации, где формальная документация может не существовать. Когда другие программисты видят, что большинство вызовов не содержат никаких аргументов, они будут делать то же самое, оставаясь в блаженном неведении о поведении по умолчанию, которое накладывают на вас аргументы по умолчанию.

Кроме того, стоит отметить, что руководство по стилю Google С++ отключает оба аргумента ctor (если это абсолютно необходимо) и аргументы по умолчанию для функций или методов.

Ответ 7

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

class Thingy2
{
public:
    enum Color{red,gree,blue};
    Thingy2();

    Thingy2 & color(Color);
    Color color()const;

    Thingy2 & length(double);
    double length()const;
    Thingy2 & width(double);
    double width()const;
    Thingy2 & height(double);
    double height()const;

    Thingy2 & rotationX(double);
    double rotationX()const;
    Thingy2 & rotatationY(double);
    double rotatationY()const;
    Thingy2 & rotationZ(double);
    double rotationZ()const;
}

main()
{
    // gets default rotations
    Thingy2 * foo=new Thingy2().color(ret)
        .length(1).width(4).height(9)
    // gets default color and sizes
    Thingy2 * bar=new Thingy2()
        .rotationX(0.0).rotationY(PI),rotationZ(0.5*PI);
    // everything specified.
    Thingy2 * thing=new Thingy2().color(ret)
        .length(1).width(4).height(9)
        .rotationX(0.0).rotationY(PI),rotationZ(0.5*PI);
}

Теперь при построении объектов вы можете выбрать, какие свойства переопределить и какие из них вы задали, явно указаны. Гораздо удобнее читать:)

Кроме того, вам больше не нужно запоминать порядок аргументов конструктору.

Ответ 8

Я бы пошел с параметрами по умолчанию, поэтому: в вашем примере предполагается, что параметры ctor напрямую соответствуют переменным-членам. Но что, если это не так, и вам нужно обработать параметры до того, как объект будет инициализирован. Один общий ctor был бы лучшим способом.

Ответ 9

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

Пример:

Вы можете использовать перегрузку для записи A (int x, foo & a) и A (int x), но вы не можете использовать параметр по умолчанию для записи A (int x, foo & = null).

Общее правило - использовать все, что имеет смысл, и делает код более удобочитаемым.

Ответ 10

Одна вещь, беспокоящая меня по умолчанию, заключается в том, что вы не можете указать последние параметры, но использовать значения по умолчанию для первых. Например, в вашем коде вы не можете создать Foo без имени, но заданный возраст (однако, если я правильно помню, это будет возможно в С++ 0x, с синтаксисом унифицированного построения). Иногда это имеет смысл, но это также может быть очень неудобно.

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

Ответ 11

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

foo bar[400];

В этом случае нет преимущества использовать параметр по умолчанию.

Это, безусловно, НЕ работает:

foo bar("david", 34)[400]; // NOPE

Ответ 12

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

Мне лично нравятся по умолчанию, когда это необходимо, потому что мне не нравится повторяющийся код. YMMV.