Pregunta

¿Es una buena práctica tener un constructor de clase que use parámetros predeterminados, o debería usar constructores sobrecargados por separado? Por ejemplo:

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

Cualquiera de las versiones parece funcionar, por ejemplo:

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

¿Qué estilo prefiere o recomienda y por qué?

¿Fue útil?

Solución

Definitivamente es una cuestión de estilo. Prefiero constructores con parámetros predeterminados, siempre que los parámetros tengan sentido. Las clases en el estándar también los usan, lo que habla a su favor.

Una cosa a tener en cuenta es que si tiene valores predeterminados para todos los parámetros menos uno, su clase se puede convertir implícitamente a partir de ese tipo de parámetro. Consulte este hilo para obtener más información.

Otros consejos

Iría con los argumentos predeterminados, especialmente porque C ++ no le permite encadenar constructores (por lo que terminará duplicando la lista de inicializadores, y posiblemente más, para cada sobrecarga).

Dicho esto, hay algunas trampas con argumentos predeterminados, incluido el hecho de que las constantes pueden estar en línea (y, por lo tanto, formar parte de la interfaz binaria de su clase). Otro aspecto a tener en cuenta es que agregar argumentos predeterminados puede convertir un constructor de argumentos múltiples explícito en un constructor implícito de un argumento:

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

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

Esta discusión se aplica tanto a los constructores como a los métodos y funciones.

¿Usa parámetros predeterminados?

Lo bueno es que no necesitará sobrecargar los constructores / métodos / funciones para cada caso:

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

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

Lo malo es que debe declarar su valor predeterminado en el encabezado, por lo que tiene una dependencia oculta: como cuando cambia el código de una función en línea, si cambia el valor predeterminado en el encabezado, deberá recompile todas las fuentes usando este encabezado para asegurarse de que usarán el nuevo valor predeterminado.

Si no lo hace, las fuentes seguirán utilizando el valor predeterminado anterior.

utilizando constructores / métodos / funciones sobrecargados?

Lo bueno es que si sus funciones no están alineadas, puede controlar el valor predeterminado en la fuente eligiendo cómo se comportará una función. Por ejemplo:

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

// Source

void doSomething()
{
   doSomething(25) ;
}

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

El problema es que debe mantener múltiples constructores / métodos / funciones y sus reenvíos.

En mi experiencia, los parámetros predeterminados parecen geniales en ese momento y hacen feliz mi factor de pereza, pero en el futuro estoy usando la clase y me sorprende cuando entra en juego el predeterminado. Por lo tanto, realmente no creo que sea una buena idea; es mejor tener un className :: className () y luego un className :: init ( arglist ). Solo por esa ventaja de mantenibilidad.

La respuesta de Sam da la razón de que los argumentos predeterminados son preferibles para los constructores. que sobrecargar. Solo quiero agregar que C ++ - 0x permitirá delegación de uno constructor a otro, eliminando así la necesidad de valores predeterminados.

Cualquiera de los enfoques funciona. Pero si tiene una larga lista de parámetros opcionales, cree un constructor predeterminado y luego haga que su función set devuelva una referencia a esto. Luego encadene a los colocadores.

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

Ahora, al construir los objetos, puede elegir elegir qué propiedades anular y cuáles ha establecido explícitamente. Mucho más legible :)

Además, ya no tienes que recordar el orden de los argumentos para el constructor.

Una cosa más a tener en cuenta es si la clase se puede usar o no en una matriz:

foo bar[400];

En este escenario, no hay ninguna ventaja al usar el parámetro predeterminado.

Esto ciertamente NO funcionaría:

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

Si crear constructores con argumentos es malo (como muchos argumentarían), entonces hacerlos con argumentos predeterminados es aún peor. Recientemente comencé a pensar que los argumentos de ctor son malos, porque su lógica de ctor debería ser lo más mínima posible . ¿Cómo se maneja el manejo de errores en el ctor, si alguien pasa un argumento que no tiene ningún sentido? Puede lanzar una excepción, lo cual es una mala noticia a menos que todas las personas que llaman estén preparadas para envolver cualquier " nuevo " llamadas dentro de bloques de prueba, o establecer algunos " se inicializa " variable miembro, que es una especie de truco sucio.

Por lo tanto, la única forma de asegurarse de que los argumentos pasados ??a la etapa de inicialización de su objeto es configurar un método initialize () separado donde pueda verificar el código de retorno.

El uso de argumentos predeterminados es malo por dos razones; En primer lugar, si desea agregar otro argumento al ctor, entonces está atrapado poniéndolo al principio y cambiando toda la API. Además, la mayoría de los programadores están acostumbrados a descubrir una API por la forma en que se usa en la práctica; esto es especialmente verdadero para las API no públicas utilizadas dentro de una organización donde la documentación formal puede no existir. Cuando otros programadores ven que la mayoría de las llamadas no contienen ningún argumento, harán lo mismo, sin darse cuenta del comportamiento predeterminado que sus argumentos predeterminados les imponen.

Además, vale la pena señalar que la guía de estilo de google C ++ evita ambos argumentos de ctor (a menos que sea absolutamente necesario) y argumentos predeterminados para funciones o métodos .

Iría con los parámetros predeterminados, por este motivo: su ejemplo supone que los parámetros de ctor corresponden directamente a las variables miembro. Pero, ¿y si ese no es el caso, y tiene que procesar los parámetros antes de que se inicialice el objeto? Tener un ctor común sería la mejor manera de hacerlo.

Una cosa que me molesta con los parámetros predeterminados es que no puede especificar los últimos parámetros sino utilizar los valores predeterminados para los primeros. Por ejemplo, en su código, no puede crear un Foo sin nombre pero con una edad determinada (sin embargo, si no recuerdo mal, esto será posible en C ++ 0x, con la sintaxis de construcción unificada). A veces, esto tiene sentido, pero también puede ser realmente incómodo.

En mi opinión, no existe una regla general. Personalmente, tiendo a usar múltiples constructores (o métodos) sobrecargados, excepto si solo el último argumento necesita un valor predeterminado.

Principalmente elección personal. Sin embargo, la sobrecarga puede hacer cualquier cosa que el parámetro predeterminado pueda hacer, pero no viceversa.

Ejemplo:

Puede usar la sobrecarga para escribir A (int x, foo & amp; a) y A (int x), pero no puede usar el parámetro predeterminado para escribir A (int x, foo & amp; = null).

La regla general es usar lo que tenga sentido y haga que el código sea más legible.

Cuestión de estilo, pero como Matt dijo, definitivamente considere marcar constructores con argumentos predeterminados que permitirían la conversión implícita como 'explícita' para evitar la conversión automática no intencional. No es un requisito (y puede que no sea preferible si está creando una clase contenedora a la que desea convertir implícitamente), pero puede evitar errores.

Personalmente me gustan los valores predeterminados cuando corresponde, porque no me gusta el código repetido. YMMV.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top