Параметры по умолчанию с конструкторами C ++ [закрыты]

StackOverflow https://stackoverflow.com/questions/187640

Вопрос

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

// 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);

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

Это было полезно?

Решение

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

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

Другие советы

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

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

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

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

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

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

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

// 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
}

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

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

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

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

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

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

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

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

foo bar[400];

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

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

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

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

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

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

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

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

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

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

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

Пример:

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top