2008-12-09 20 views
8

Donde trabajo, personas en su mayoría piensan que los objetos se inicializan mejor usando C++ - la construcción de estilo (entre paréntesis), mientras que los tipos primitivos deben ser inicializadas con el operador =:¿Por qué usar = para inicializar un tipo primitivo en C++?

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

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

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

Respuesta

4

A menos que haya demostrado que es importante con respecto al rendimiento, no me preocuparía una copia adicional utilizando el operador de asignación en su ejemplo (std::string foo = "Foo";). Me sorprendería mucho que esa copia incluso exista una vez que mires 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 usado 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 definitivo.

+1

¡Ah! Sí, así será: en C, el operador = era la única manera, por lo que todavía "se siente bien" para los veteranos. Gracias. –

0

Es un problema de estilo. Incluso la afirmación de que "std :: string =" Foo "; sería ineficaz porque implicaría una copia adicional" no es correcta. Esta "copia extra" es eliminada por el compilador.

17

Inicializar variables con el operador = o con una llamada de constructor son semánticamente las mismas, es solo una cuestión de estilo. Prefiero el operador =, ya que se lee de forma más natural.

Usando el operador = generalmente no genera una copia adicional; simplemente llama al constructor normal. Tenga en cuenta, sin embargo, que con tipos no primitivos, esto es solo para inicializaciones que ocurren al mismo tiempo que las declaraciones. Compare:

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 C++ standard, sección 8.5, párrafo 14 estados:

De lo contrario (es decir, para los casos de copia de inicialización restantes), se crea un temporal. Las secuencias de conversión definidas por el usuario que pueden convertir desde el tipo de fuente al tipo de destino o una clase derivada de las mismas se enumeran (13.3.1.4), y la mejor se elige a través de la resolución de sobrecarga (13.3). La conversión definida por el usuario así seleccionada se llama para convertir la expresión del inicializador 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.) En ciertos casos, se permite que una implementación elimine el temporal inicializando el objeto directamente; ver 12.2.

parte de la Sección 12.2 estados:

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

+1

usando = creará una cadena temporal, y que inicializar el objeto usando esa secuencia temporal con el constructor de copia. (Esta es la razón de que forma se llama copia de inicialización, y la junta de cadena ("foo") es llamada inicialización directa). Otra diferencia es que the = "foo" requiere un ctor implícito. –

+0

sin embargo, el compilador puede eliminar el temporal que se crea y pasa al constructor de copia. por lo que es posible que no notes que se está creando temporalmente. –

+0

Artículo de Herb Sutter sobre 'inicialización directa' frente a 'inicialización de copia': http://www.gotw.ca/gotw/036.htm –

2

usted probablemente encontrará que el código como

std::string strFoo = "Foo"; 

será evitar hacer una copia extra y compila el mismo código (una llamada de un constructor de un solo argumento) como el que tiene paréntesis.

Por otro lado, hay casos donde debe usar paréntesis, como una lista de inicialización de miembros de constructor.

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

1

Bueno, quién sabe qué ellos piensan, pero también prefiero = para los tipos primitivos, principalmente porque no son objetos, y porque esa es la forma "habitual" de inicializarlos.

+0

En C++, los objetos de tipo primitivo son objetos, simplemente no son objetos de clase. –

+0

¿qué? ¿desde cuando? Estoy hablando de objetos en el sentido de OOP. Los tipos primitivos de C++ definitivamente no son objetos. – hasen

+0

sí. todo excepto referencias, función y tipos de vacíos son objetos. –

0

Creo que es más un hábito, muy pocos objetos podrían inicializarse usando =, la cadena es uno de ellos. También es una forma de hacer lo que dijiste "usando paréntesis en todas partes (que el lenguaje te permite usarlo)"

11

Acabo de sentir la necesidad de otra publicación absurda.

string str1 = "foo"; 

se llama copia-inicialización, porque lo que hace el compilador, si no se elide cualquier temporales, es:

string str1(string("foo")); 

lado de la comprobación de que el constructor de conversión utilizado es implícita. De hecho, todas las conversiones implícitas están definidas por el estándar 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 constraste,

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, se puede desactivar elidiendo de provisionales mediante el uso de -fno-elide-constructores:

-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. 

La norma 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 ambos formularios. 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 la primera en alguna situación, porque quieren eliminar la ambigüedad de la declaración:

T u(v(a)); 

Migh mirar a alguien como una definición de una variable u que se inicializa el uso de un temporal de un tipo v que obtiene un parámetro para su constructor llamado a. Pero, en realidad, lo que el compilador hace con esto es la siguiente:

T u(v a); 

crea una declaración de función que toma un argumento de tipo v, y con un parámetro llamado a. Así que la gente hace

T u = v(a); 

para eliminar la ambigüedad que, a pesar de que podría haber hecho

T u((v(a))); 

también, porque 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 la función también :)

+1

Histórico: En el caso de los tipos primitivos, T a = u fue la única forma permitida durante mucho tiempo. La segunda forma T a (u) se introdujo en el estándar que se utilizará en las listas de inicialización, y para los tipos primitivos es equivalente. –

0

un argumento que se podría hacer para:

foo std :: string ("barra");

es que mantiene las cosas de la misma incluso si los cambios en el recuento de argumentos, es decir .:

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

No funciona con el signo '='.

Otra cosa es que para muchos objetos a '=' se siente poco natural, por ejemplo, supongamos que tiene una clase Array donde el argumento da la longitud:

matriz arr = 5;

no se siente bien, ya que no construimos una matriz con el valor 5, pero con una longitud de 5:

matriz arr (5);

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

+0

La palabra clave 'explícita' se usa para ese fin, para asegurarse de que "Array arr = 5" no compila. Tienes que decir "Array arr (5)" si el constructor Array (int) se declara explícito. –

0

Pero solo para confundirlo aún más inicializa las primitivas en la lista de inicialización usando la sintaxis del objeto.

foo::foo() 
    ,anInt(0) 
    ,aFloat(0.0) 
{ 
} 
Cuestiones relacionadas