2010-02-13 8 views
31

Sé que en C++ 03, técnicamente no es necesario que la plantilla std::basic_string tenga memoria contigua. Sin embargo, tengo curiosidad de cuántas implementaciones existen para los compiladores modernos que realmente aprovechan esta libertad. Por ejemplo, si uno quiere usar basic_string para recibir los resultados de alguna API de C (como en el ejemplo a continuación), parece tonto asignar un vector solo para convertirlo en una cadena inmediatamente.¿Es razonable usar std :: basic_string <t> como un búfer contiguo cuando se dirige a C++ 03?

Ejemplo:

DWORD valueLength = 0; 
DWORD type; 
LONG errorCheck = RegQueryValueExW(
     hWin32, 
     value.c_str(), 
     NULL, 
     &type, 
     NULL, 
     &valueLength); 

if (errorCheck != ERROR_SUCCESS) 
    WindowsApiException::Throw(errorCheck); 
else if (valueLength == 0) 
    return std::wstring(); 

std::wstring buffer; 
do 
{ 
    buffer.resize(valueLength/sizeof(wchar_t)); 
    errorCheck = RegQueryValueExW(
      hWin32, 
      value.c_str(), 
      NULL, 
      &type, 
      &buffer[0], 
      &valueLength); 
} while (errorCheck == ERROR_MORE_DATA); 

if (errorCheck != ERROR_SUCCESS) 
    WindowsApiException::Throw(errorCheck); 

return buffer; 

sé código como este podría reducir ligeramente la portabilidad, ya que implica que es contigua std::wstring - pero me pregunto hasta qué punto no portables que hace este código. Dicho de otra manera, ¿cómo pueden los compiladores aprovechar la libertad que tiene la memoria no contigua?


EDIT: Actualicé esta pregunta para mencionar C++ 03. Los lectores deben tener en cuenta que al apuntar a C++ 11, el estándar ahora requiere que basic_string sea contiguo, por lo que la pregunta anterior no es un problema al apuntar a ese estándar.

+0

A menos que esté seguro de que MSVC le está proporcionando con éxito el RVO (aunque tiene dos devoluciones diferentes, una temporal y otra variable), entonces no se le "permite" preocuparse por una copia adicional; -) –

+1

No creo que RVO pueda optimizar una copia entre el vector y la cadena .... –

+0

Lo que quiero decir es que si el código actual no tiene RVO, entonces es "crear cadena. Copiarlo al valor de retorno". Estás hablando de un 50% más de copia si cambias eso a "crear vector. Cópialo a cadena".Cópielo para devolver el valor ". O tal vez no haga ninguna copia extra si lo hace' devuelva std :: wstring (vec.begin(), vec.end()); 'y obtenga" create vector. Copia para devolver el valor (a través de RVO) ". Me preocuparía si podía detectar la diferencia de velocidad antes de preocuparme por lo portátil que era el código resultante. Pero ese es solo el ejemplo, por eso es un comentario, no una respuesta. –

Respuesta

23

Considero que es bastante seguro asumir que std :: string asigna su almacenamiento de forma contigua.

En la actualidad, todas las implementaciones conocidas de std::string asignan espacio contiguamente.

Por otra parte, el actual proyecto de C++ 0x (N3000) [Editar: Advertencia, enlace directo a gran PDF] requiere que el espacio se asignará de manera contigua (§21.4.1/5):

El carbón -like objetos en un objeto basic_string se deben almacenar contiguamente. Es decir, para cualquier basic_string objeto s, la identidad & * (s.begin() + n) == & * s.begin() + n ocupará para todos los valores de n, tales que 0 < = n < s.size().

Como tal, las posibilidades de una implementación actual o futura de std::string usando el almacenamiento no contiguo son esencialmente nulas.

+1

"todas las implementaciones conocidas". En particular, todo lo que importa para una llamada de WinAPI son las diversas versiones de Windows. Entonces, "todas las implementaciones conocidas" podrían ser "todas las implementaciones". –

+5

@Steve Jessop: en realidad, 'std :: basic_string' es una característica del compilador, no una característica de Windows. En este caso, no importa la versión de Windows en la que se ejecuta el código compilado. –

+3

Punto justo. Sin embargo, podría decir: "este código solo es compatible con los compiladores de Microsoft". Todavía no es estrictamente lo mismo que las versiones de Windows, pero el punto es que solo tiene que preocuparse por un conjunto fijo de implementaciones. Los futuros compiladores de MS admitirán la mayoría o la totalidad de C++ 0x. –

0

El resultado es indefinido y no lo haría. El costo de leer en un vector y luego convertirlo en una cadena es trivial en los montones modernos de C++. VS el riesgo de que su código fallezca en Windows 9

también, ¿no necesita un const_cast en & buffer [0]?

+2

Las implementaciones de cadenas no tienen nada que ver con la API de Windows y, por lo tanto, no deberían tener nada que ver con qué versión de Windows usa alguien. Sí, es un comportamiento indefinido según el estándar. Pero está bien para cada compilador del que tengo conocimiento. Tengo curiosidad de cuántos compiladores realmente aprovechan la latitud que les da el estándar. –

+0

nuevas versiones de Windows con la nueva versión de c runtime. El punto es que indefinido significa que puede cambiar misteriosamente en el futuro, ¿por qué tomar el riesgo? Prácticamente, nunca he visto una secuencia de cuerda que no muestre la secuencia como una buena matriz clásica. Pero aún no lo haría – pm100

+0

Indefinido NO significa que pueda cambiar misteriosamente en el futuro. Indefinido significa que los compiladores pueden implementarlo como quieran. Una vez que se compila el código, su comportamiento no puede cambiar, a menos que llame a bibliotecas dinámicas. Como la cadena no llama a las DLL, las versiones futuras de Windows no la romperán. (A menos que use un tiempo de ejecución de C dinámico, entonces supongo que es posible pero aún poco probable), no estoy preguntando si es una buena idea hacer esto. Estoy preguntando si hay compiladores que se preocupen. –

-2

Por supuesto, asignar un vector aquí es una tontería. Usar std :: wstring aquí tampoco es sabio. Es mejor usar una matriz de caracteres para llamar a los winapi. construir un wstring al devolver el valor.

+0

Hice esto como un ejemplo, suponiendo que estoy leyendo un valor de cadena Unicode del registro. Utiliza cualquier función de Win32 que te guste y la pregunta es la misma. –

12

Hace un tiempo hubo una pregunta acerca de ser capaz de escribir en el almacenamiento para un std::string como si se tratara de una gran variedad de personajes, y que giraba en torno a si el contenido de un std::string eran contiguas:

Mi respuesta indicó que de acuerdo a una pareja bien fuentes (Herb Sutter y Matt Austern) la corriente estándar de C++ requiere std::string para almacenar su contigua de datos bajo ciertas condiciones (una vez que se llama aconsiderado 10 suponiendo str es un std::string) y ese hecho prácticamente obliga a la implementación.

Básicamente, si combina las promesas hechas por string::data() y string::operator[]() concluye que &str[0] necesita devolver un búfer contiguo. Por lo tanto, Austern sugiere que el comité simplemente lo haga explícito, y aparentemente eso es lo que sucederá en el estándar 0x (¿o lo están llamando el estándar 1x ahora?).

De manera estricta, una implementación no tiene que implementar std::string usando almacenamiento contiguo, pero tiene que hacerlo prácticamente a pedido. Y su código de ejemplo lo hace al pasar en &buffer[0].

Enlaces:

0

Editar: Quiere llamar &buffer[0], no buffer.data(), porque devuelve un [] no const referencia y hace notificar al objeto que su contenido puede cambiar inesperadamente.


Sería más limpio que hacer buffer.data(), pero debe preocuparse menos de memoria contigua de memoria compartida entre las estructuras. Las implementaciones string pueden y deben esperar que se les informe cuando se modifica un objeto. string::data específicamente requiere que el programa no modifique el búfer interno devuelto.

MUCHAS altas posibilidades de que alguna implementación creará un búfer para todas las cadenas sin inicializar además de tener la longitud establecida en 10 o lo que sea.

Utilice un vector o incluso una matriz con new[]/delete[]. Si realmente no puede copiar el búfer, inicialice legalmente la cadena a algo único antes de cambiarlo.

+0

Es por eso que llamo 'std :: basic_string :: resize' primero. El redimensionamiento de llamadas obliga esencialmente a una reasignación del búfer subyacente que está utilizando el objeto de cadena. Consulte el artículo n.º 16 de STL eficaz de Scott Myers: "Sepa cómo pasar datos de vectores y cadenas a las API heredadas". –

+0

@Billy: Vi lo que estás haciendo. "Esencialmente las fuerzas" no son "garantías". Desde la perspectiva de la implementación, tiene una serie de objetos que * deberían * contener todos los ceros, y nunca se le dio la oportunidad de ver si lo hacen o no porque nunca llamó a una función de miembro no '' después de 'cambiar el tamaño '. – Potatoswatter

+0

Umm .. 'resize' en sí mismo es una función miembro no const. El redimensionamiento de llamadas fuerza a la implementación a asignar y construir de forma predeterminada los valores en la cadena; el cambio de tamaño modifica la cadena en sí misma. Por lo tanto, incluso en una implementación de referencia contada, la cadena debe crearse desde cero, porque el contenido de la cadena ha cambiado. Creo que estás confundiendo 'resize' con' reserve' aquí. 'Reserve' cambia la asignación subyacente pero no los datos, por lo que es posible que una implementación pueda compartir.Pero 'resize' cambia ambos, ergo no sharing. –

Cuestiones relacionadas