¿Cómo representar cadenas en una aplicación multiplataforma (Windows, iOS, Android) C ++?

Estoy desarrollando una aplicación cuya base de código central sería multiplataforma para Windows, iOS y Android.

Mi pregunta es: ¿cómo debería representar internamente las cadenas utilizadas por esta aplicación para poder utilizarlas de manera efectiva en las tres plataforms?

Es importante tener en count que uso DirectWrite en gran medida en Windows, de las cuales las funciones de la API suelen esperar que se pase wchar_t * (por cierto, la documentation de la API indica que "Un puntero a una matriz de caracteres Unicode"). saber si esto significa que están en encoding UTF-16 o no)

Veo tres enfoques diferentes (sin embargo, me resulta bastante difícil comprender los detalles de event handling cadenas Unicode con C ++ de manera multiplataforma, así que tal vez extraño un concepto importante):

  • use std :: string internamente en todas partes (y almacene las cadenas en la encoding UTF-8?), y conviértalos a wchar_t * donde sea necesario para la API DirectWrite (no sé lo que necesitan las API de procesamiento de text de Android e iOS todavía).
  • use std :: wstring internamente en todas partes. Si entiendo las cosas bien, esto no sería efectivo desde la perspectiva del uso de la memory, porque un wchar_t es de 4 bytes en iOS y Android (y significa que tendría que almacenar la cadena en UTF-16 en Windows y en UTF-32 en Android / iOS?)
  • cree una abstracción para cadenas con una class base abstracta e implemente el almacenamiento interno específicamente para las diferentes plataforms.

¿Cuál sería la mejor solución? Y, por cierto, ¿existen bibliotecas multiplataforma que resuelvan el event handling cadenas? (y también, lectura y serialization de cadenas Unicode)

(ACTUALIZACIÓN: borró la parte con la pregunta sobre la diferencia de char * y std :: string).

Solutions Collecting From Web of "¿Cómo representar cadenas en una aplicación multiplataforma (Windows, iOS, Android) C ++?"

Una parte de mi pregunta proviene de mis malentendidos, o no entiendo completamente cómo funcionan las classs de strings y cadenas en C ++ (vengo de background C #). Las diferencias de los dos y pros y contras se han descrito en esta excelente respuesta: std :: wstring VS std :: string .

Cómo funciona la secuencia y el wstring

Para mí, el descubrimiento más importante sobre classs de strings y strings fue que semánticamente no representan una pieza de text codificado , sino simplemente una "cadena" de caracteres o wchar_t. Se parecen más a una matriz de datos simple con algunas operaciones específicas de cadena (como añadir y substr) en lugar de representar text. Ninguno de ellos conoce ningún tipo de encoding de cadenas, manejan cada elemento char o wchar_t individualmente como un carácter separado.

Codificaciones

Sin embargo, en la mayoría de los sistemas, si crea una cadena a partir de un literal de cadena con un carácter especial como este:

std::string s("ű"); 

El carácter will será representado por más de un byte en la memory, pero eso no tiene nada que ver con la class std :: string , que es una característica del comstackdor ya que puede codificar literales de cadena con UTF8 (no todos los comstackdores). (Y los literales de cadena prefijados con L serán representados por wchar_t-s en UTF16 o UTF32 u otra cosa, según el comstackdor).
Por lo tanto, la cadena "ű" se representará en la memory con dos bytes: 0xC5 0xB1 , y la class std :: string no sabrá que esos dos bytes significan semánticamente un carácter (un punto de código Unicode) en UTF8, de ahí el código de muestra :

 std::string s("ű"); std::cout << s.length() << std::endl; std::cout << s.substr(0, 1); 

produce el siguiente resultado (según el comstackdor, algunos comstackdores no toman los literales de cadena como UTF8, y algunos comstackdores dependen de la encoding del file fuente):

 2   

La function size () devuelve 2, porque lo único que la cadena std :: sabe es que almacena dos bytes (dos caracteres). Y substr también funciona "primitivamente", devuelve una cadena que contiene el carácter único 0xC5 , que se muestra como , porque no es un carácter UTF8 válido (pero eso no molesta a la cadena std ::).

Y a partir de ahí podemos ver que quienes manejan las codificaciones son las diversas API de procesamiento de text de la plataforma, como el simple cout o DirectWrite .

Mi acercamiento

En mi aplicación DirectWrite es muy importante, que solo acepta cadenas codificadas en UTF16 (en forma de wchar_t * pointers). Así que decidí almacenar las cadenas tanto en la memory como en el file codificado en UTF16. Sin embargo, quería una implementación multiplataforma que pueda manejar las cadenas UTF16 en Windows, Android e iOS, lo que no es posible con std :: wstring , porque su tamaño de datos (y la encoding que se adapta a su uso) depende de la plataforma.

Para crear una plataforma de cadena multiplataforma, estrictamente UTF16, planifiqué basic_string en un tipo de datos de 2 bytes de longitud . Sorprendentemente, al less para mí, casi no encontré información sobre esto en línea, basé mi solución en este enfoque. Aquí está el código:

 // Define this on every platform to be 16 bytes! typedef unsigned short char16; struct char16_traits { typedef char16 _E; typedef _E char_type; typedef int int_type; typedef std::streampos pos_type; typedef std::streamoff off_type; typedef std::mbstate_t state_type; static void assign(_E& _X, const _E& _Y) {_X = _Y; } static bool eq(const _E& _X, const _E& _Y) {return (_X == _Y); } static bool lt(const _E& _X, const _E& _Y) {return (_X < _Y); } static int compare(const _E *_U, const _E *_V, size_t _N) {return (memcmp(_U, _V, _N * 2)); } static size_t length(const _E *_U) { size_t count = 0; while(_U[count] != 0) { count++; } return count; } static _E * copy(_E *_U, const _E *_V, size_t _N) {return ((_E *)memcpy(_U, _V, _N * 2)); } static const _E * find(const _E *_U, size_t _N, const _E& _C) { for(int i = 0; i < _N; ++i) { if(_U[i] == _C) { return &_U[i]; } } return 0; } static _E * move(_E *_U, const _E *_V, size_t _N) {return ((_E *)memmove(_U, _V, _N * 2)); } static _E * assign(_E *_U, size_t _N, const _E& _C) { for(size_t i = 0; i < _N; ++i) { assign(_U[i], _C); } return _U; } static _E to_char_type(const int_type& _C) {return ((_E)_C); } static int_type to_int_type(const _E& _C) {return ((int_type)(_C)); } static bool eq_int_type(const int_type& _X, const int_type& _Y) {return (_X == _Y); } static int_type eof() {return (EOF); } static int_type not_eof(const int_type& _C) {return (_C != eof() ? _C : !eof()); } }; typedef std::basic_string<unsigned short, char16_traits> utf16string; 

Las cadenas se almacenan con la class anterior y los datos en bruto de UTF16 se pasan a las funciones de API específicas de las distintas plataforms, todo lo cual en este momento parece ser compatible con la encoding UTF16.
La implementación puede no ser perfecta, sin embargo, las funciones anexar, substr y tamaño parecen funcionar correctamente. Todavía no tengo mucha experiencia con el event handling cadenas en C ++, así que siéntete libre de comentar / editar si indiqué algo incorrectamente.

La diferencia entre std :: strings y char * es que la class std :: string usa características de C ++ y char * no. Un std :: string es una class de caracteres de contenedor y define methods convenientes para usarlo, un char * es un puntero a alguna memory con la que puede trabajar.

Si buscas alguna class base que sea independiente de la plataforma, te apuntaría a QString . Esto es parte de la biblioteca Qt que tiene como objective llegar a implementaciones independientes de C ++. También es OpenSource , por lo que puede usarlo para tener una idea de cómo otros implementan cadenas independientes de plataforma. La documentation también es muy buena.

Implementar una class abstracta para representar de forma diferente en cada plataforma parece una mala idea. Implementación y testings de trabajo extra (en cada plataforma) y agregará más gastos generales que simplemente usando std :: wstring (por supuesto, podría contrarrestar la sobrecarga al no usar una class abstracta, sino usar #ifdefs para cambiar la implementación, pero aún extra trabajo).

Usando std :: string o std :: wstring en todas partes parece ser el path a seguir, implementa algunas funciones de utilidad para convertir la cadena que elijas al formatting dependiente del sistema y no tendrás problemas. Estoy trabajando en un proyecto multiplataforma, que ya se ejecuta en iOS, Windows, Linux y Mac, en este proyecto usé multibyte std :: string y no tuve muchos problemas, nunca usé std :: wstring pero no lo hice. No ve por qué no funcionaría.