www.digitalmars.com Home | Search | D | Comments
Last update Sat Feb 25 11:48:33 2006
D
Language
Phobos
Comparisons

· D vs C/C++/C#/Java
· Rationale for Builtins
· Converting C to D
· Converting C++ to D
· The C Preprocessor vs D
· D strings vs C++ std::string
· D complex vs C++ std::complex
· D Contract Programming vs C++

Strings em D vs Strings em C++

Por que ter strings na linguagem núcleo de D ao invés de inteiramente em uma biblioceta como strings em C++? Por que esse ponto? Onde está a melhoria?

Operador de Concatenação

Strings em C++ são cravadas com a sobrecarga de operadores existentes A escolha óbvia para concatenação é += e +. Mas alguns apenas olhando para o código irão ver + e pensar "adição". Eles terão que buscar os tipos (e tipos são freqüêntemente entarrados atrás de múltplos typedef's) para ver que é um tipo string, e não está somando strings mas sim concatenando-as.

Adicionalmente, se alguém tem uma ordem de floats, é '+' sobrecarregado para ser o mesmo que adição de ordens ou concatenação de ordens?

Em D, estes problemas são evitados introduzindo um novo operador binário ~ como operador de concatenação. Ele trabalha com ordens (das quais strings são um subconjunto). ~= é o operador de junção correspondente. ~ em ordens de floats seria concatená-los, + seria insinuar uma soma de vetor. Adicionar um novo operador torna possível ortogonalidade e consistência no tratamento de ordens. (Em D, strings são simples ordens de caracteres, não um tipo especial.)

Interoperabilidade com a Sintaxe de String de C

Sobrecarga de operadores trabalha somente se um dos operandos for sobrecarregável. Então a classe string do C++ não pode controlar consistentemente expressões arbitrárias contento strings. Considere:
 const char abc[5] = "world";
string str = "hello" + abc;
Que não trabalhará. Mas isso trabalha quando a linguagem núcleo conhece strings:
	const char[5] abc = "world";
char[] str = "hello" ~ abc;

Consistência com a Sintaxe de String de C

Há três modos de encontrar o comprimento de uma string em C++:
	const char abc[] = "world"; : sizeof(abc)/sizeof(abc[0])-1
: strlen(abc)
string str : str.length()
Esse tipo de inconsistência torna difícil escrever gabaritos genéricos. Considere D:
	char[5] abc = "world"; : abc.length
char[] str : str.length

Checando por Strings Vazias

Strings em C++ usam uma função para determinar se uma string está vazia:
	string str;
if (str.empty())
// string está vazia
Em D, uma string vazia tem comprimento zero:
	char[] str;
if (!str.length)
// string é vazia

Redimensionando Strings Existentes

C++ controla isso com a função membro resize():
	string str;
str.resize(novotamanho);
D toma vantagem de saber que str é uma string, então redimensioná-la é apenas mudar a propriedade comprimento:
	char[] str;
str.length = novotamanho;

Fatiando uma String

C++ fatia uma string existente usando um construtor especial:
	string s1 = "hello world";
string s2(s1, 6, 5); // s2 é "world"
D tem a sintaxe de fatia de ordem, não possível em C++:
	char[] s1 = "hello world";
char[] s2 = s1[6 .. 11]; // s2 é "world"
Fatiar, claro, trabalha com qualquer ordem em D, não apenas strings.

Copiando uma String

C++ copia strings com a função replace:
	string s1 = "hello world";
string s2 = "goodbye ";
s2.replace(8, 5, s1, 6, 5); // s2 é "goodbye world"
D usa a sintaxe de fatia como um lvalue:
	char[] s1 = "hello world";
char[] s2 = "goodbye ";
s2[8..13] = s1[6..11]; // s2 é "goodbye world"

Conversões para Strings C

Isso é necessário para compatibilidade comAPI's C. Em C++, isso usa a função membro c_str():
	void foo(const char *);
string s1;
foo(s1.c_str());
Em D, strings podem ser implicitamente convertidas para char*:
	void foo(char *);
char[] s1;
foo(s1);
Nota: alguns irão discutir que é um engano em D tem uma conversão implicita de char[] para char*.

Checagem de Limites da Ordem

Em C++, checagem de limites da ordem de strings para [] não é feita. Em D, checagem de limites da ordem é ligada por padrão e pode ser desligada com um interrruptor do compilador depois de o programa ser depurado.

Instruções Switch com Strings

Não são possíveis em C++, nem há qualquer modo de adicioná-las adicionando mais para uma biblioteca. Em D, elas tomam a forma sintática óbvia:
	switch (str)
{
case "hello":
case "world":
...
}
onde str pode ser qualquer "string" literal, strings de ordem fixa como char[10], ou strings dinâmicas como char[]. Uma implementação de qualidade pode, é claro, explorar muitas estratégias de implementar eficientemente isso baseado em conteúdos das strings caso.

Preenchendo uma String

Em C++, isso é feito com a função membro replace replace():
	string str = "hello";
str.replace(1,2,2,'?'); // str é "h??lo"
Em D, use a sintaxe de fatia de ordem de forma natural:
	char[] str = "hello";
str[1..3] = '?'; // str é "h??lo"

Valor vs Referência

Strings em C++, como implementadas pela STLport, são por valor e são terminadas em 0. [A mais recente é uma implementação alternativa, mas STLport parece ser a implementação mais popular.] Isso, com a falta de coleta de lixo, tem algumas conseqüências. Primeiro de tudo, qualquer string criada deve fazer sua própria cópia dos dados da string. O 'proprietário' dos dados da string deve ser mantido, porque quando o proprietário é deletado todas as referências se tornam inválidas. Se alguém tenta evitar o problema de referência ociosa  tratando strings como tipos de valor, haverão muitos overheads de alocação de memória, cópia de dados, e desalocação de memória. Depois, a terminação em 0 implica que strings não podem se referir a outras strings. Dados da string no segmento de dados, pilha, etc., não podem se referir.

Strings em D são tipos de referência, e a memória tem coleta de lixo automática. Isso significa que somente referências precisam ser copiadas, não os dados da string. Strings em D podem se referir a dados no segmento de dados estático, dados na pilha, dados dentro de outras strings, objetos, buffers de arquivos, etc. Não é necessário manter o 'proprietário' dos dados da string.

A questão óbvia é se múltiplas strings em D se referem aos mesmos dados de string, o que acontece se os dados são modificados? Todas as referência irão apontar agora para os ados modificados. Isso pode ter suas próprias conseqüências, que podem ser evidades se a convenção de cópia-na-escrita for seguida. Tudo que cópia-na-escrita é é que se uma string é escrita, uma cópia atual dos dados da string é feita antes.

O resultado de strings em D sendo somente referência e com coleta de lixo é que código que faz muitas manipulações de string, como um compressor lzw, pode ser muito mais eficiênte em termos de consumo de memória e velocidade.

Benchmark

Vamos dar uma olhada em um pequeno utilitário, conta-palavras, que conta a freqüência de cada palavra em um arquivo de texto. Em D, pareceria com isso:
import file;

int main (char[][] args)
{
int w_total;
int l_total;
int c_total;
int[char[]] dictionary;

printf(" lines words bytes file\n");
for (int i = 1; i < args.length; ++i)
{
char[] input;
int w_cnt, l_cnt, c_cnt;
int inword;
int wstart;

input = cast(char[])file.read(args[i]);

for (int j = 0; j < input.length; j++)
{ char c;

c = input[j];
if (c == '\n')
++l_cnt;
if (c >= '0' && c <= '9')
{
}
else if (c >= 'a' && c <= 'z' ||
c >= 'A' && c <= 'Z')
{
if (!inword)
{
wstart = j;
inword = 1;
++w_cnt;
}
}
else if (inword)
{ char[] word = input[wstart .. j];

dictionary[word]++;
inword = 0;
}
++c_cnt;
}
if (inword)
{ char[] w = input[wstart .. input.length];
dictionary[w]++;
}
printf("%8lu%8lu%8lu %.*s\n", l_cnt, w_cnt, c_cnt, args[i]);
l_total += l_cnt;
w_total += w_cnt;
c_total += c_cnt;
}

if (args.length > 2)
{
printf("--------------------------------------\n%8lu%8lu%8lu total",
l_total, w_total, c_total);
}

printf("--------------------------------------\n");

foreach (char[] word1; dictionary.keys.sort)
{
printf("%3d %.*s\n", dictionary[word1], word1);
}
return 0;
}

Duas pessoas escreveram implementações em C++ usando a biblioteca padrão de gabaritos C++, wccpp1 e wccpp2. O arquivo de entrada alice30.txt é o texto de "Alice no País das Maravilhas." O compilador D, dmd, e o compilador C++, dmc, compartilham o mesmo otimizador e gerador de código, que provê uma comparação da eficiência da semântica das linguagens ao invés da sofisticação do gerador de código e otimização. Testes foram executados em uma máquina com Win XP. dmc usa STLport para a implementação de gabaritos.

Programa Compilação Tempo de Compilação Execução Tempo de Execução
D wc dmd wc -O -release 0.0719 wc alice30.txt >log 0.0326
C++ wccpp1 dmc wccpp1 -o -I\dm\stlport\stlport 2.1917 wccpp1 alice30.txt >log 0.0944
C++ wccpp2 dmc wccpp2 -o -I\dm\stlport\stlport 2.0463 wccpp2 alice30.txt >log 0.1012

Os seguintes testes foram executados no linux, novamente comparando um compilador D (gdc) e um compilador C++ (g++) que compartilham um otimizador e gerador de código comuns. O sistema é Pentium III 800MHz rodando RedHat Linux 8.0 e gcc 3.4.2. O compilador Digital Mars D para linux (dmd) é incluído para comparação.

Programa Compilação Tempo de Compilação Execução Tempo de Execução
 wc gdc -O2 -frelease -o wc wc.d 0.326 wc alice30.txt > /dev/null 0.041
D wc dmd wc -O -release 0.235 wc alice30.txt > /dev/null 0.041
C++ wccpp1 g++ -O2 -o wccpp1 wccpp1.cc 2.874 wccpp1 alice30.txt > /dev/null 0.086
C++ wccpp2 g++ -O2 -o wccpp2 wccpp2.cc 2.886 wccpp2 alice30.txt > /dev/null 0.095

Estes testes comparam gdc com g++ em um PowerMac G5 2x2.0GHz rodando MacOS X 10.3.5 e gcc 3.4.2. (Tempos são um pouco menos precisos.)

Programa Compilação Tempo de Compilação Execução Tempo de Execução
D wc gdc -O2 -frelease -o wc wc.d 0.28 wc alice30.txt > /dev/null 0.03
C++ wccpp1 g++ -O2 -o wccpp1 wccpp1.cc 1.90 wccpp1 alice30.txt > /dev/null 0.07
C++ wccpp2 g++ -O2 -o wccpp2 wccpp2.cc 1.88 wccpp2 alice30.txt > /dev/null 0.08

wccpp2 por Allan Odgaard

#include <algorithm>
#include <cstdio>
#include <fstream>
#include <iterator>
#include <map>
#include <vector>

bool isWordStartChar (char c) { return isalpha(c); }
bool isWordEndChar (char c) { return !isalnum(c); }

int main (int argc, char const* argv[])
{
using namespace std;
printf("Lines Words Bytes File:\n");

map<string, int> dict;
int tLines = 0, tWords = 0, tBytes = 0;
for(int i = 1; i < argc; i++)
{
ifstream file(argv[i]);
istreambuf_iterator<char> from(file.rdbuf()), to;
vector<char> v(from, to);
vector<char>::iterator first = v.begin(), last = v.end(), bow, eow;

int numLines = count(first, last, '\n');
int numWords = 0;
int numBytes = last - first;

for(eow = first; eow != last; )
{
bow = find_if(eow, last, isWordStartChar);
eow = find_if(bow, last, isWordEndChar);
if(bow != eow)
++dict[string(bow, eow)], ++numWords;
}

printf("%5d %5d %5d %s\n", numLines, numWords, numBytes, argv[i]);

tLines += numLines;
tWords += numWords;
tBytes += numBytes;
}

if(argc > 2)
printf("-----------------------\n%5d %5d %5d\n", tLines, tWords, tBytes);
printf("-----------------------\n\n");

for(map<string, int>::const_iterator it = dict.begin(); it != dict.end(); ++it)
printf("%5d %s\n", it->second, it->first.c_str());

return 0;
}