Pregunta

Donde trabajo, la mayoría de las personas piensan que los objetos se inicializan mejor usando una construcción de estilo C ++ (con paréntesis), mientras que los tipos primitivos deben inicializarse con el operador =:

std::string strFoo( "Foo" );
int nBar = 5;

Sin embargo, nadie parece poder explicar por qué prefieren las cosas de esta manera. Puedo ver que std::string = "Foo"; sería ineficiente porque implicaría una copia adicional, pero ¿qué hay de malo en desterrar al operador = por completo y usar paréntesis en todas partes?

¿Es una convención común? ¿Cuál es el pensamiento detrás de esto?

¿Fue útil?

Solución

A menos que haya demostrado que es importante con respecto al rendimiento, no me preocuparía por una copia adicional utilizando el operador de asignación en su ejemplo (std::string foo = "Foo";). Me sorprendería bastante que si esa copia existe incluso una vez que miras el código optimizado, creo que realmente llamará al constructor parametrizado apropiado.

En respuesta a su pregunta, sí, diría que es una convención bastante común. Clásicamente, las personas han utilizado la asignación para inicializar tipos incorporados, y no hay una razón convincente para cambiar la tradición. La legibilidad y el hábito son razones perfectamente válidas para esta convención dado el poco impacto que tiene en el código final.

Otros consejos

Las variables de inicialización con el operador = o con una llamada de constructor son semánticamente iguales, es solo una cuestión de estilo. Prefiero el operador =, ya que se lee más naturalmente.

Usar el operador = generalmente no genera una copia adicional, solo llama al constructor normal. Sin embargo, tenga en cuenta que con los tipos no primitivos, esto es solo para las inicializaciones que ocurren al mismo tiempo que las declaraciones. Comparar:

std::string strFooA("Foo");  // Calls std::string(const char*) constructor
std::string strFoo = "Foo";  // Calls std::string(const char*) constructor
                             // This is a valid (and standard) compiler optimization.

std::string strFoo;  // Calls std::string() default constructor
strFoo = "Foo";      // Calls std::string::operator = (const char*)

Cuando tiene constructores predeterminados no triviales, la última construcción puede ser un poco más ineficiente.

El estándar C ++ , sección 8.5 , el párrafo 14 establece:

  

De lo contrario (es decir, para los casos restantes de inicialización de copia), se crea un temporal. Se enumeran las secuencias de conversión definidas por el usuario que pueden convertir del tipo de origen al tipo de destino o una clase derivada del mismo (13.3.1.4), y la mejor se elige mediante resolución de sobrecarga (13.3). La conversión definida por el usuario así seleccionada se llama para convertir la expresión inicializadora en temporal, cuyo tipo es el tipo devuelto por la llamada de la función de conversión definida por el usuario, con los calificadores cv   del tipo de destino. Si la conversión no se puede hacer o es ambigua, la inicialización está mal formada. El objeto que se inicializa se inicializa directamente   desde el temporal de acuerdo con las reglas anteriores. 87 ) En ciertos casos, una implementación puede eliminar el temporal inicializando el objeto directamente; ver 12.2.

Parte de la sección 12.2 establece:

  

Incluso cuando se evita la creación del objeto temporal, todas las restricciones semánticas deben respetarse como si se hubiera creado el objeto temporal. [Ejemplo:   incluso si no se llama al constructor de la copia, se cumplirán todas las restricciones semánticas, como la accesibilidad (11). ]

Sentí la necesidad de otra publicación tonta y tonta.

string str1 = "foo";

se llama copy-initialization , porque lo que hace el compilador, si no elude ningún temporario, es:

string str1(string("foo")); 

además de verificar que el constructor de conversión utilizado es implícito. De hecho, el estándar define todas las conversiones implícitas en términos de inicialización de copia. Se dice que una conversión implícita de tipo U a tipo T es válida, si

T t = u; // u of type U

es válido.

En contraste,

string str1("foo");

está haciendo exactamente lo que está escrito y se llama inicialización directa . También funciona con constructores explícitos.

Por cierto, puede deshabilitar la eliminación de temporarios utilizando -fno-elide-constructors:

-fno-elide-constructors
    The C++ standard allows an implementation to omit creating a temporary which 
    is only used to initialize another object of the same type. Specifying this 
    option disables that optimization, and forces G++ to call the copy constructor 
    in all cases.

El Estándar dice que prácticamente no hay diferencia entre

T a = u;

y

T a(u);

si T y el tipo de u son tipos primitivos. Entonces puedes usar ambas formas. Creo que es solo el estilo lo que hace que las personas usen la primera forma en lugar de la segunda.


Algunas personas pueden usar el primero en alguna situación, porque quieren desambiguar la declaración:

T u(v(a));

podría parecer a alguien como una definición de una variable u que se inicializa usando un temporal de un tipo v que obtiene un parámetro para su constructor llamado a. Pero, de hecho, lo que hace el compilador con esto es esto:

T u(v a);

Crea una declaración de función que toma un argumento de tipo <=>, y con un parámetro llamado <=>. Entonces la gente lo hace

T u = v(a);

para desambiguar eso, a pesar de que podrían haberlo hecho

T u((v(a)));

también, debido a que nunca hay paréntesis alrededor de los parámetros de función, el compilador lo leería como una definición de variable en lugar de una declaración de función también :)

Probablemente encontrará ese código como

std::string strFoo = "Foo";

evitará hacer una copia adicional y compilará el mismo código (una llamada de un constructor de argumento único) que el que está entre paréntesis.

Por otro lado, hay casos en que uno debe usar paréntesis, como una lista de inicialización de miembros constructores.

Creo que el uso de = o paréntesis para construir variables locales es en gran medida una cuestión de elección personal.

Bueno, quién sabe lo que piensan , pero también prefiero el = para los tipos primitivos, principalmente porque no son objetos, y porque ese es el " usual " forma de inicializarlos.

Es una cuestión de estilo. Incluso la afirmación de que & Quot; std :: string = & Quot; Foo & Quot ;; sería ineficiente porque implicaría una copia adicional " no es correcto. Este & Quot; copia extra & Quot; es eliminado por el compilador.

Creo que es más un hábito, se pueden inicializar muy pocos objetos usando =, la cadena es uno de ellos. También es una forma de hacer lo que dijiste & Quot; usando paréntesis en todas partes (que el lenguaje te permite usarlo) & Quot;

Un argumento que uno podría hacer para:

std :: string foo (" bar ");

Es que mantiene las cosas igual incluso si el recuento de argumentos cambia, es decir:

std :: string foo (" bar " ;, 5);

No funciona con un signo '='.

Otra cosa es que para muchos objetos un '=' se siente antinatural, por ejemplo, digamos que tiene una clase Array donde el argumento da la longitud:

Array arr = 5;

No se siente bien, ya que no construimos una matriz con el valor 5, sino con la longitud 5:

Array arr (5);

se siente más natural, ya que está construyendo un objeto con el parámetro dado, no solo copiando un valor.

Pero para confundirte aún más, inicializas primitivas en la lista de inicialización usando la sintaxis de objeto.

foo::foo()   
  ,anInt(0)   
  ,aFloat(0.0)   
{   
}   
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top