Inkscape, curvas de Bézier, SVG e Python
Nesse texto, pretendo introduzir o leitor ao desenvolvimento de programas que façam uso de curvas de Bézier e, como metodologia de ensino experimental, pretendo usar conhecimentos relacionados (a ferramenta Inkscape e o padrão SVG) como auxílio.
Edição vetorial
Chegamos em um ponto onde os computadores transformaram nossos hábitos diários e estão presentes nas mais variadas partes de nossas vidas. Uma das áreas que foram modernizadas pelos computadores é a área de tratamento de imagens.
A forma mais comum utilizada para representar imagens em computadores é através de matrizes onde cada ponto contém um nível de intensidade das cores vermelho, verde e azul. Os monitores funcionam de forma parecida. O problema com essa abordagem é que se precisarmos modificar a imagem, mudando o tamanho, a posição ou o ângulo da imagem, a imagem passará por uma edição destrutiva, onde há a perda de qualidade na imagem original. Começando pelo exemplo mais simples, veja as imagens abaixo:
Note como a imagem, ao aplicarmos um “zoom”, torna-se quadriculada. É possível remover esse aspecto aplicando alguns filtros, mas não importa o filtro que escolhermos, ele irá provocar alguma perda de informação. Filtros especializados são populares, pois costumam conseguir resultados melhores para casos específicos (fotos, pixel art, …). A esse tipo de imagem, utilizamos o termo raster.
A solução que encontramos para o problema de qualidade perfeita independente de resolução foram imagens vetoriais. Imagens vetoriais são imagens que, no lugar de ter uma representação dos pontos da imagem, possuem um conjunto de descrições de curvas, que são utilizadas para gerar a imagem raster em qualquer resolução, mantendo as características da curva. A imagem abaixo ilustra bem a diferença entre imagens raster e imagens vetoriais:
Esse tipo de imagem ainda não é dominante, pois é mais difícil de trabalhar com elas para certos tipos de trabalho. Entretanto, para alguns tipos de trabalho (como logotipos), elas são um requisito.
Inkscape
Inkscape é uma ferramenta open source para trabalharmos com imagens vetoriais que vem sendo desenvolvida há bastante tempo.
A principal ferramenta que usamos para trabalhar com imagens vetoriais é a curva de Bézier. No Inkscape, você pode pressionar Shift + F6 para selecioná-la.
A curva de Bézier (do ponto de vista do Inkscape) é definida por nós de controle e você pode mudar certos atributos de cada nó para fazer a curva se tornar mais aberta, fechada ou cúspide.
O formato que o Inkscape usa para salvar o seu trabalho é o SVG, um padrão aberto da W3C (a mesma entidade que define vários padrões utilizados pela web) baseado em XML, que, em breve, será abordado novamente.
O foco desse artigo não é transformá-lo em um grande designer, então isso é tudo que será comentado sobre o Inkscape.
Python
Python é uma linguagem de programação razoavelmente popular com um incrível poder expressivo que inspira vários programadores. Nesse texto ela será utilizada para a demonstração dos algoritmos e os códigos costumam ser bem legíveis, sem que nenhum treinamento especial seja necessário para o entendimento. Entretanto, utilizo alguns poucos conceitos que talvez sejam incomuns, então resolvi documentá-los aqui, para facilitar na futura compreensão de tais códigos.
For
Em Python, o for atua da forma que, em outras linguagens, atua o for-each. Uma função comumente utilizada quando você precisa manipular elementos através de índices é a função range, que recebe um argumento inteiro N e retorna uma lista contendo os números no intervalo [0;N). A saída de range(10), por exemplo, é [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].
Fatias/slices
Em Python, existe um operador para fatiar listas (e similares). Ele possui a mesma sintaxe do operador de índice. O código a seguir resume bem algumas das operações possíveis com o mesmo:
>>> a = [1, 2, 3] # Inicializa a com uma lista >>> a[0] # Acessa o primeiro elemento de a 1 >>> a[0:3] # Acessa o intervalo [0;3) [1, 2, 3] >>> a[:] # Os argumentos padrões são 0 e o tamanho do intervalo [1, 2, 3] >>> a[1:] # Outro exemplo de argumentos padrões [2, 3] >>> a[:-1] # Intervalo do primeiro elemento até o penúltimo (-1 exclui um elemento do final) [1, 2]
Listas e tuplas
Em Python, os dois tipos principais para se trabalhar com cadeias são listas e tuplas. Colchetes são utilizados para criar listas, enquanto parênteses são utilizados para criar tuplas. A diferença entre listas e tuplas é que tuplas são imutáveis. Para converter entre um tipo e outro, basta passar o objeto como argumento do construtor do outro tipo.
Sobrecarregando o operador de chamada de função
Em Python, tudo é um objeto (incluindo as funções) e é possível sobrecarregar até o ponto. Para sobrecarregar o operador de chamada de função, basta definir a função-membro __call__ e, daí em diante, você poderá usar o objeto com o operador sobrecarregado como se fosse uma função.
Considerações finais sobre Python
Nesse texto, o código-fonte apresentado utiliza tuplas para representar pontos e, apesar de não haver verificações de segurança, assumimos que um ponto pode ser de qualquer dimensão (na verdade não). Isso implica que sempre utilizamos repetição para preencher os valores dos pontos.
Curvas de Bézier
Há vários modos que podemos utilizar para descrever curvas e cada abordagem reflete uma definição diferente e possui diferentes limitações. Se usarmos funções matemáticas fundamentais, como x^2 ou 2*x para representar curvas, não poderemos representar curvas que passam pela mesma linha em y mais de uma vez, uma limitação inaceitável. Utilizar funções complicadas também não é uma solução viável, pois a instabilidade numérica dos algoritmos utilizados pode estragar o resultado final. Felizmente, foram criadas as curvas de Bézier, que resolvem todos esses problemas.
A nossa jornada para explorar as curvas de Bézier começa com uma fórmula matemática bem simples, acompanhada de seu algoritmo em Python (por que usar pseudo-algoritmos se temos uma linguagem mais expressiva, fácil e inambígua?):

class l(object):
def __init__(self, p1, p2):
self.p1 = p1
self.p2 = p2
def __call__(self, t):
ret = list(self.p1)
for i in range(len(ret)):
ret[i] = (1 - t) * self.p1[i] + t * self.p2[i]
return tuple(ret)
linha1 = l((0, 0), (2, 1))
A função l(t) define uma linha que passa pelos pontos p1 e p2. No código-fonte, foi criada uma classe que permite definir uma família dessas funções, com diferentes pontos p1 e p2. Para obter um ponto presente na linha, você passa um valor t qualquer. É fácil perceber que o valor de l(0) é igual a p1 e o valor de l(1) é igual a p2.
Usando dois pontos e a fórmula anterior é possível definir uma linha reta. Podemos adicionar um terceiro ponto e a “mesma fórmula” para definir a curva que vai da linha (a fórmula anterior usava pontos) l1 até a linha l2. Com 3 pontos, a fórmula é transformada no seguinte:

class f(object):
def __init__(self, p1, p2, p3):
self.p1 = p1
self.p2 = p2
self.p3 = p3
def __call__(self, t):
ret = list(self.p1)
for i in range(len(ret)):
ret[i] = (1 - t ** 2) * self.p1[i] + 2 * t * (1 - t) * self.p2[i] + t ** 2 * self.p3[i]
return tuple(ret)
curva1 = f((0, 0), (0, 1), (1, 1))
Toda a ideia de curvas de Bézier é uma generalização desse princípio: Dada uma lista de pontos, nós queremos descrever uma curva que viaja do ponto p0 até o ponto pn guiada pelos pontos intermediários. Uma curva de Bézier composta por N pontos de controle possui grau N – 1. O código anterior se traduz na seguinte animação:
Você pode (e deveria) brincar com curvas de Bézier nesse site (que não necessita do Flash Player ou do Java).
Algoritmo de De Casteljau
Agora que sabemos o que é uma curva de Bézier, devemos começar a nos preocupar como podemos renderizá-las e esse é justamente o propósito do algoritmo de De Casteljau, um algoritmo recursivo lento, porém numericamente estável.
A propriedade explorada pelo algoritmo de De Casteljau para renderizar linhas de Bézier é que qualquer curva de Bézier pode ser dividida em duas, conectadas fim-a-fim, produzindo o mesmo resultado (lembre-se que é um algoritmo recursivo).
def draw_curve(curve):
if is_flat(curve):
draw_segments(curve)
else:
pieces = subdivide(curve)
draw_curve(pieces[0])
draw_curve(pieces[1])
No código anterior, a função is_flat informa quando uma curva está indistinguível de uma reta (uma forma de heurística) e a função subdivide divide uma curva de Bézier em duas. Esse pequeno código simples é suficiente para entender as diferentes partes do algoritmo. Há uma condição para parar a recursão e duas sub-tarefas (seguindo o princípio de dividir para conquistar).
O problema será quebrado, primeiramente, com a solução para o problema de dividir uma curva de Bézier cúbica em duas. Ao tentar encontrar o ponto onde t=1/2 em uma curva de Bézier, encontramos o ponto que usaremos para “cortá-la” em duas. Na imagem a seguir, esse é o ponto azul ciano:
Existe uma afirmação de que a curva formada pelos pontos P0, P1, P2 e P3 é igual a junção da curva formada pelos pontos P0, m0, q0, B(1/2) com a curva formada pelos pontos B(1/2), q1, m2 e P3. A prova dessa afirmação existe, porém não será feita abaixo, pois ela é de pouco interesse (nem tanto…) para o nosso objetivo. Note que os pontos m0, q0, m1, q1, m2 são os pontos médios dos segmentos de retas anteriores e são facilmente computáveis. O código Python a seguir é capaz de computar esses pontos:
def midpoint(p1, p2):
ret = list(p1)
for i in range(len(ret)):
ret[i] = (p1[i] + p2[i]) / 2.
return tuple(ret)
def midpoints(points):
ret = points[:-1]
for i in range(len(ret)):
ret[i] = midpoint(points[i], points[i+1])
return ret
def subdivide(curve):
first = midpoints(curve) # m
second = midpoints(first) # q
third = midpoints(second) # B(1/2)
return [[curve[0], first[0], second[0], third[0]], \
[third[0], second[1], first[2], curve[3]]]
Note que o algoritmo serve para curvas de Bézier cúbicas (formadas por 4 pontos). Agora que temos um algoritmo para dividir as curvas de Bézier, a implementação restante para sermos capazes de utilizar o algoritmo de De Casteljau é a “heurística” que informa se a curva já está “reta” o suficiente.
Uma heurística que podemos utilizar e possui boa estabilidade numérica computa a distância, num instante t, entre onde a curva está e onde ela deveria estar caso fosse uma linha reta. Chamemos essa função de d(t), então o valor de t que queremos utilizar para a função heurística é o valor onde d(t) é maior (maximizar). Após cancelar, reduzir e otimizar várias operações, chegamos a seguinte função (que só funciona para pontos bidimensionais):
def is_flat(curve):
tol = 25
ax = 3. * curve[1][0] - 2. * curve[0][0] - curve[3][0]
ay = 3. * curve[1][1] - 2. * curve[0][1] - curve[3][1]
bx = 3. * curve[2][0] - curve[0][0] - 2. * curve[3][0]
by = 3. * curve[2][1] - curve[0][1] - 2. * curve[3][1]
return max(ax*ax, bx*bx) + max(ay*ay, by*by) <= tol
SVG
A abordagem usada até o momento requer que compartilhemos nossas imagens vetoriais através de longos textos descritivos ou códigos feito em Python, uma abordagem inviável caso queiramos compartilhar e colaborar nossos trabalhos. Para resolver esses problemas, o padrão SVG foi criado.
Como comentado anteriormente, SVG é o padrão aberto mais difundido para se trabalhar com imagens vetoriais. O SVG é baseado em XML (sendo assim, legível para humanos) que contém descrições sobre curvas de Bézier (e outros conceitos que o tornam mais expressivo e poderoso).
O elemento raiz de um SVG é o svg e seu esqueleto básico pode ser visto abaixo:
<svg xmlns="http://www.w3.org/2000/svg"> <!-- documento aqui --> </svg>
O exemplo anterior possui um comentário que indica onde colocamos o conteúdo dos nossos trabalhos, em SVG. O elemento que utilizamos para criar linhas de Bézier é o elemento path, que recebe um atributo d, capaz de controlar, de forma análoga, os movimentos que você pode fazer com um lápis. O exemplo abaixo será útil para explicar características importantes:
<svg xmlns="http://www.w3.org/2000/svg"> <path stroke="black" d="M 0 0 L 50 50" /> <path stroke="blue" d="M 0 50 L 25 0" stroke-width="4" /> </svg>
O documento acima possui dois novos elementos do tipo path. Eles desenham duas linhas diagonais para ajudar você a entender o sistema de coordenadas do SVG e possuem traços de preenchimento com largura e cor diferentes, para diferenciá-los mais facilmente.
O sistema de coordenadas é parecido com o desenho torto exemplificado acima. Você tem o “centro” no canto superior esquerdo e anotamos as coordenadas na forma x y. É diferente da forma que comumente fazemos em matemática, mas se você é um programador já deve estar acostumado com esse sistema.
Outra característica interessante para se notar é que os documentos são desenhados na ordem que foram definidos. Nesse caso, a “linha azul” irá sobrepor a “linha preta”, pois foi definida por último.
O atributo stroke define a cor de contorno (que até agora é o próprio caminho) e ela pode ser especificada de forma similar a HTML/CSS. Uma alternativa para a cor azul, usada anteriormente como blue, seria “#0000ff“. O atributo stroke-width determina a largura do contorno. A largura de contorno padrão é 1.
Para especificarmos o caminho, usamos uma linguagem que trabalha com uma sequência de instruções. A instrução M recebe uma coordenada como argumento e serve para “movimentar o lápis”. A instrução L também recebe uma coordenada como argumento e serve para fazer uma linha. Assim, essa “máquina” mantém um estado e a ordem em que você poe as instruções influencia no resultado.
Caso você tente fazer duas linhas não colineares com o que aprendeu, vai perceber que um triângulo preenchido será desenhado, pois o padrão é que a cor de preenchimento do objeto seja a cor preta. Para desativar o preenchimento, use o atributo fill com o argumento none (fill=”none”).
Hora de aprender a desenhar curvas de Bézier, o momento pelo qual estávamos esperando! O comando Q faz curvas de Bézier de quadráticas (que precisam de 3 pontos, sendo o primeiro o ponto no qual o “lápis” está), enquanto o comando C faz curvas de Bézier cúbicas (que precisam de 4 pontos). Veja o exemplo abaixo (e não fique só olhando os códigos de exemplo, teste-os):
<svg xmlns="http://www.w3.org/2000/svg"> <path stroke="blue" d="M 0 0 Q 50 0 0 50" fill="none" /> <path stroke="red" d="M 0 0 C 50,0 50,50 0,50" fill="none" /> </svg>
Se você não sabia que vírgulas podem ser utilizadas como separadores de coordenadas, deve ter percebido com esse último exemplo. Outra coisa legal é que se você, em vez de digitar um comando, simplesmente colocar uma coordenada, a “máquina” entenderá como um comando L, por padrão.
Referências
Esse “trabalho” foi baseado no excelente texto de Jeremy Kun publicado no blog Math ∩ Programming, que é licenciado sob a Creative Commons Attribution-NonCommercial 3.0 Unported. Outra fonte importante foi o SVG Primer, hospedado na W3C.
Imagens vindas da Wikipédia e de outras fontes estão “corretamente” linkadas para a fonte original. Demais imagens são autoria própria (e por isso estão mais “feias”).
Palavras escrotas na língua portuguesa
Diariamente eu aprendo (será?) uma palavra nova no meu agregador de feeds RSS e após muitos meses assinando o serviço, eu fiquei insatisfeito com as palavras escrotas que eu conhecia. Esse post é um top 5 dessas palavras (na verdade há várias palavras piores que foram censuradas graças a um certo critério…):
#5: odaxelagnia
Excitação sexual associada ao acto de morder.
#4: gimnosofista
Diz-se de ou filósofo hindu que, na Índia antiga, se dedicava à contemplação mística, andando por vezes quase nu.
Realmente há uma palavra de semântica tão específica assim na língua portuguesa? POR QUE??
#3: tutear
Chamar ou tratar por tu.
#2: dendroclasta
Que ou pessoa que não respeita as árvores.
#1: quelonografia
Estudo que faz a descrição das tartarugas.
Final
Minha dica, procurem por palavras começando com gimno ou quilo.
E uma palavra bônus, que não entrou na lista anterior, porque apesar de escrota, é uma palavra que realmente tem utilidade: defenestração.
E, por último: O adjetivo escroto é uma palavra com significado bem definido, mas, nesse post, foi utilizada a gíria.
Só pelo prazer de transformar o parágrafo anterior em uma mentira: tenha um bom dia/noite/noite-e-dia.
Tufão 1.0.0
After a long time developing Tufão, it finally reached 1.0 version some hours ago. I’ve spent a lot of time cleaning up the API and exploring the features provided by C++11 and Qt5 to release this version.
This is the first Tufão release that:
- … breaks config files, so you’ll need to update your config files to use them with the new version
- … breaks ABI, so you’ll need to recompile your projects to use the new version
- … breaks API, so you’ll need to change your source code to be able to recompile your previous code and use the new version
- … breaks previous documented behaviour, so you’ll need to change the way you use features that were available before. But don’t worry, because the list of these changes are really small and are well documented below.
Porting to Tufão 1.0
From now on, you should link against tufao1 instead of tufao. The PKGCONFIG, qmake and CMake files were renamed also, so you can have different Tufão libraries in the same system if their major version differs.
The list of behavioural changes are:
- Headers are being stored using a Hash-table, so you can’t easily predict (and shouldn’t) the order of the headers anymore. I hope this change will improve the performance.
- HttpServerRequest::ready signal auto-disconnects all slots connected to the HttpServerRequest::data and HttpServerRequest::end slots before being emitted.
- HttpFileServer can automatically detect the mime type from the served files, so if you had your own code logic to handle the mimes, you should throw it away and enjoy a lesser code base to maintain.
Most of the errors related to API changes are caught at compile-time and you can use the Qt5′s new connection syntax and C++11′s override keyword to catch the remaining errors.
News
The list of changes:
- The project finally have a logo (made by me in Inkscape)
- Deprecated API was removed
- Url and QueryString removed in favor of QUrl
- Headers refactored to inherit from QMultiHash instead of QMultiMap
- HttpServerResponse
- Constructor’s options argument is optional now
- setOptions method added
- Constructor takes a reference to a QIODevice instead a pointer
- HttpServerRequest
- Constructor takes a reference to a QAbstractSocket instead a pointer
- socket method returns a reference instead a pointer
- url returns a QUrl
- data signal was changed and you must use readBody method to access body’s content
- the upgrade’s head data is accessed from the request body from now on
- now the object auto-disconnects slots from data and end signals right before emit ready
- setCustomData and customData methods added
- Now HttpServerRequestRouter use these methods to pass the list of captured texts
- HttpServer uses reference instead of pointers in several places
- AbstractHttpServerRequestRouter refactored to explore lambdas features
- Tufão’s plugin system fully refactored
- It’s using JSON files as configuration
- AbstractHttpServerRequestHandler::handleRequest
- It uses references instead pointers
- It receives 2 arguments instead of 3
- One more abstraction to sessions created to explore lambdas
- WebSocket
- startServerHandshake is taking references instead pointers
- LESS POINTERS and MORE REFERENCES
- This change exposes a model more predictive and natural
- I’m caring less about Qt style and more about C++ style
- But don’t worry, I’ll maintain a balance
- Using scoped enums
- HttpFileServer uses/sends mime info
- Interfaces don’t inherit from QObject anymore, so you can use multiple inheritance to make the same class implement many interfaces
- HttpUpgradeRouter introduced
- HttpServer::setUpgradeHandler also
- Updated QtCreator plugin to work with QtCreator 2.7.0 and Qt 5
The future
I want to improve the Tufão’s stability and performance, so now I’ll focus on a minor relase with unit testing and some minor changes.
After Tufão 1.0.1, I’ll focus on BlowThemAll.
Bonus
You can see a visualization video based on the history of commits below.
This release deserves a wallpaper, so I made one. See it below:
You can download the source of the previous wallpaper here.
What are you still doing here? Go download the new version right now!
Why does /prog/ hate Java?
What can happen in an anonymous discussion board about programming languages? I found this ancient post made in 4chan. Enjoy it.
Why does /prog/ hate Java?
11 Name: Anonymous : 2007-06-13 04:47 ID:8/uv1if0My god, I’m >>2 and you’ve all mentioned retarded/irrelevant/wrong stuff.
I don’t fucking care it’s interpreted. I don’t fucking care people calls some languages “scripting languages” and thinks worse of them because of a name (what do you think these people are for labelling, then judging by labels?). I don’t fucking care it’s slower than C/Asm (any language other than C/Asm is slower than C/Asm, that’s the point: a contract trading execution speed for development speed and fun, which saves lots of money because developers are more expensive than hardware).
Java’s problems and the reason why I said it sucks are:
1. Lame ass object system shoved up your ass because the language doesn’t properly support any programming paradigm: not good OO (not even decent), not good good old imperative (objects up ass), functional programming not (really) possible, others absolutely impossible. You can’t model your application properly when the language designers are forcing stupid rules on you. Lacks multiple inheritance. Interfaces are a stupid hack to make the lack of multiple inheritance look less bad.
2. Yet built-in types are not objects. Way to go. This introduces wrapper classes (which are pure shit) and difficults heterogeneous collections.
3. Static typing with all sorts of anal rules that don’t really reduce development or debugging time, don’t really make stupid programmers write reliable code, and really pisses some smart programmers off.
4. Lacks builtin lists or dictionaries.
5. Lacks many advanced features you’d ask of a modern language which attempts to be productive: first class functions, first class classes, anonymous functions (now it has anonymous classes, not the same, objects up ass, etc.), static nested scoping (closures), mixins, introspection, dynamically created and modified functions, classes, and inheritance, …
6. Can’t override operators. If you think implementing a method “add” is less potentially dangerous than implementing an perator or method “+”, you’re pretty fucking stupid.
7. The standard library is a stinking fucking piece of shit, the worst ever created by man. It’s pure blOOat, making you create three instances of three different classes just to open a fucking file! And have a look at the two failed attempts at dates and the second one’s class hierarchy! Factory classes are pretty fucking stupid! sin, cos, tan are not methods! “a”.equals(“b”) is pretty fucking ugly (and a result of not being able to overload operators)! It’s all full of stupid shit and insanity, it’s hyperstructured, it’s overgeneralized, it’s ENTERPRISE.
8. It encourages terrible programmers and terrible ways which they call “best practices”, “enterprise-grade software” and “code patterns”. There’s a whole universe of wankery over the worst pieces of shit ever designed by man. Best practices considered harmful.
9. In practice, it’s terribly unproductive. Anything written in pretty much any other high-level language, from Lisp to Python, from PHP to Perl, from OCaml to Haskell is 5-10 times shorter, 5-10 times faster to write, 5-10 times easier to maintain, and 5-10 times more flexible.
10. Its world is full of businessspeak, pointy-haired managers (all worthless), and retards who went for a 20 hour Java “programming” course and think they can code. It has a terrible community, I’m starting to think even PHP “designers” are better. And every site talking about Java doesn’t say (or know) shit, and is all about “scalable enterprise-grade Web 2.0 XML-based AJAX-based professional n-tier business solutions”, “discovering business logic”, “lowering the Total Cost of Ownership”, “optimizing cash flows”, “converting visitors into customers”, “maximizing profits”, and “best-practices”.
Gerenciamento de recursos em linguagens de programação
É época de carnaval e provavelmente muito menos pessoas irão ler esse texto, mas, ainda… eu irei digitar, pois há essa necessidade estranha que eu tenho de me agarrar a essa pequena impressão de que o tempo está me tornando melhor, de alguma forma…
Pois bem, gerenciar recursos é um dos maiores desafios presentes em linguagens de programação e, dois indícios fortes que posso citar para sustentar esse argumento fraco é a existência do Buffer Overflow, um dos tipos de ataques mais explorados na história da computação que torna-se possível devido ao mau gerenciamento de memória, e a quantidade de recursos que investimos em ferramentas como a Valgrind.
Pretendo, nesse post, demonstrar o problema, apresentar conceitos simples e, logo após, analisar duas formas de gerenciamento de memória bem comuns, RAII e Garbage Collection.
E no princípio, tínhamos ponteiros…
Um ambiente familiar cria conforto para algumas pessoas, incluindo a mim e, sendo assim, começarei essa parte do texto com algo que me é familiar, um código-fonte:
void f(const char *filename)
{
// check some conditions
if (!filename)
return;
// ... acquire some resources
int *array = malloc(sizeof(int) * /* ... */);
if (!array)
return;
FILE *f = fopen(filename);
if (!f) {
free(array);
return;
}
// Use resources
/* ... */
// Free resources
fclose(f);
free(array);
}
Os programas que criamos gerenciam recursos e, no princípio, sequer existiam abstrações para procedimentos. O que dizer sobre abstrações para gerenciamento de recursos, então? O código anterior exemplifica o problema de lidar com recursos.
No exemplo, mesmo havendo apenas dois recursos a se gerenciar, torna-se evidente que esse modelo de programação torna o código mais complexo, dificulta a leitura e nos distancia do problema que queremos resolver, nos forçando a pensar em como os recursos se comportam e sendo fácil cometer erros como esquecer de liberar algum recurso ou liberá-los na ordem errada.
Sempre que inicializarmos um novo recurso, devemos verificar se a inicialização falhou, caso em que devemos liberar os recursos que haviam sido adquiridos antes de usar alguma construção da linguagem para quebrar o fluxo de instruções. É importante também analisar cuidadosamente o fluxo de instruções e detectar em quais momentos qualquer um dos recursos pode tornar-se inacessível, ponto em que devemos inserir mais código, redundante, de limpeza. Por último, e não menos importante, é uma boa prática liberar os recursos na ordem contrária em que eles foram adquiridos, para evitar referências inválidas, caso no qual um recurso referencia um outro recurso que já foi liberado.
Como eu chamaria toda essa bagunça, confusão, sujeira, desordem, porcaria, trapalhada, barafunda, balbúrdia, embaraço… que esse modelo de programação está criando? Código espaguete?
Formalizando…
Há o gerenciamento errôneo de um recurso quando:
- Um recurso é destruído antes de ser usado. Nesse caso referências inválidas para o recurso irão existir e o uso dessas referências pode criar um comportamento imprevisível cuja origem será difícil de rastrear ou o encerramento da aplicação.
- O recurso não é destruído após ser utilizado. Nesse caso chamamos o erro de vazamento de recurso e isso impede que poder computacional seja reciclado para outra tarefa.
Podemos evitar o primeiro problema deixando de fazer a limpeza de recursos manualmente, mas com as técnicas que (não) discutimos, essa decisão implica o segundo problema.
Há três definições diferentes que podemos usar para nos referir a vazamento de recursos.
A definição do usuário
Do ponto de vista do usuário:
Um recurso vazado é qualquer recurso que não pode ser possivelmente utilizado para qualquer propósito útil.
A definição poderia ser simplificada para “qualquer recurso que eu, como usuário, não estou interessado”, mas essa definição incluiria o uso de cache, por exemplo, o que seria inutilizável na nossa pesquisa.
A definição do desenvolvedor
Do ponto de vista do desenvolvedor:
Um recurso vazado é qualquer recurso que não é alcançável.
Um recurso alcançável é qualquer recurso para o qual exista uma referência, também alcançável, que você possa utilizar para acessá-lo. Deve-se notar que essa é uma definição recursiva e, quando o recurso observado é memória, podemos quebrar essa recursão afirmando que qualquer objeto alocado na stack é alcançável.
Essa é uma definição bem mais formal e, como consequência, é mais fácil criar uma ferramenta para detectar vazamentos de recursos quando utilizamos essa definição.
Entretanto, ela é menos abrangente que a definição do ponto de vista de usuário e um exemplo simples que, segundo essa definição, não apresenta nenhum vazamento de recursos, mas, de acordo com a definição do ponto de vista de usuário, possui um vazamento de recurso, segue:
Buffer buffer = new Buffer();
while (true) {
String in = System.console.readLine("...");
if (in.equals("*"))
break;
byte buf[] = new byte[1000000];
buffer.add(buf);
// do something with buf
}
No exemplo anterior, a variável buffer contém referência a um recurso que não é mais útil quando o fluxo de repetição presente é quebrado, mas, ainda assim, o recurso é alcançável. Essa situação exemplifica a diferença entre as duas definições.
É garantido que um objeto inalcançável não será usado novamente, mas o contrário não é verdade. Algumas pessoas sugerem que essa definição seja uma forma de “aproximação”.
A definição do depurador
Do ponto de vista do depurador:
Um recurso vazado é um recurso que não foi destruído durante o encerramento da aplicação.
Essa é uma definição muito usada em programas de detecção de vazamentos de memória, como Valgrind ou Visual Studio.
Uma situação onde essa definição se distingue da definição do ponto de vista do usuário é a mesma situação demonstrada anteriormente.
Outra situação onde ela se distingue é quando um recurso é utilizado até o final da aplicação, mas não é destruído. Caso o recurso em questão seja memória, isso não costuma ser um problema, pois sistemas operacionais modernos irão lidar com essa situação. Além disso, o objetivo do programa é solucionar um problema do usuário e, assim, a definição do usuário deveria ter precedência caso não haja outras questões envolvidas (como manutenção de código, desempenho, …).
Garbage Collection
Uma das soluções que foi sugerida para resolver esse problema é chamada de Garbage Collection. A premissa é que o desenvolvedor não deveria se preocupar com o gerenciamento de recursos e alguma outra ferramenta, como o compilador ou o interpretador da máquina virtual, é que devia lidar com essa tarefa. Essa técnica depende de uma estratégia e um algoritmo e é a solução que linguagens como Java adotam.
Para automatizar o gerenciamento de memória, a definição do ponto de vista do programador é utilizada. Então a pergunta que resta para solucionar o problema é “como podemos saber que um objeto nunca mais será usado novamente?”.
Estratégias de Garbage Collection decidem quando a limpeza de recursos não utilizados deve ocorrer e podem ser eventos como “quando a memória acabar” ou “a cada 5 minutos”. Alguns algoritmos serão discutidos isoladamente a seguir, mas eles não deveriam depender de uma estratégia específica. Uma forma de conseguir um algoritmo de Garbage Collection desacoplado da estratégia é projetando um algoritmo in situ.
Mark and Sweep
Mark and Sweep é um algoritmo de Garbage Collection que obriga que todos os objetos tenham o campo mark bit e cujo funcionamento acontece em duas fases, mark e sweep.
Na fase mark, o algoritmo deve identificar os objetos alcançáveis, marcando-os através do campo mark bit. Um pseudocódigo para essa fase segue:
def mark(objects):
for obj in objects:
if !obj.isMarked():
obj.setMark(True)
mark(obj.children)
mark(root)
No pseudocódigo anterior, root é a variável que contém os objetos raízes, que são objetos localizados na stack.
Na fase sweep, todos os objetos devem ser vasculhados. Os que tiverem o campo mark bit com o valor falso devem ser destruídos e os objetos restantes devem ter o campo mark bit atribuídos ao valor falso. Um pseudocódigo segue:
for obj in objects:
if obj.isMarked():
del obj
for obj in objects:
obj.setMark(False)
O algoritmo apresentado não é in situ, como observado, pela recursão que acontece na fase mark. É possível remover essa recursão usando uma lista, mas a lista em si faz com que o algoritmo não seja in situ. Um truque usado para transformar o algoritmo mark and sweep em um algoritmo in situ é inverter os ponteiros quando nós os seguimos, fazendo o objeto observado apontar para o objeto pai. Dessa forma, podemos armazenar a trajetória de quais objetos foram observados sem usar qualquer espaço extra quando o algoritmo é executado.
Stop and Copy
No algoritmo Stop and Copy, a região de memória é dividida em duas metades, uma reservada para a aplicação e outra reservada para o Garbage Collector. Quando o algoritmo é executado, ele começa a rastrear os objetos alcançáveis e, sempre que um novo objeto alcançável é encontrado, ele é movido para a região de memória reservada para o Garbage Collector e toda referência para esse objeto é atualizada. Ao final do processo, a região de memória reservada ao Garbage Collector passa a ser a região de memória reservada a aplicação e vice-versa.
Uma forma de atualizar as referências aos objetos é, no momento em que um objeto for copiado, utilizar a região na qual ele residia para armazenar um ponteiro que aponta para sua nova região.
Garantir a propriedade de algoritmo in situ nesse caso é bem mais fácil e uma forma de se fazer isso seria dividir a região reservada ao Garbage Collector em 3 partes, sendo a primeira a de objetos analisados e copiados, a segunda apenas de copiados e a terceira a região livre. Tudo que é necessário para manter esse modelo de memória são dois ponteiros, além do ponteiro que aponta para o começo da região de memória.
Exceptions
Exceções formam uma técnica de tratamento de erros que visa diminuir o número de erros e melhorar a manutenção de código. Um assunto interessante, de fato, porém, não é um objetivo desse texto explicá-las. É um objetivo, entretanto, explicar que elas impõem novas restrições nas soluções de gerenciamento de recursos. Considere o seguinte código:
FileReader file = /* ... */; // ... file.close();
No exemplo anterior, o que acontece quando uma exceção é lançada antes do arquivo ser fechado? A linguagem Java permite que objetos tenham destrutores, métodos que executam instruções quando a máquina virtual destruir o objeto. Nada impede que a classe FileReader implemente um destrutor que chame o método close, mas, por outro lado, não há garantias de quando, ou mesmo se, o objeto será destruído. O comportamento não é determinístico.
A falta de determinismo pode parecer um problema pequeno a olhos desatentos, mas é um problema tão sério que o tratamento de exceções de linguagens como Java tem uma palavra-chave extra, a palavra-chave finally.
Considere o caso em que o programa fica aberto por bastante tempo ou trata uma quantidade de arquivos razoavelmente grande. Vários sistemas operacionais seguros costumam impor limites configuráveis aos processos e há um limite também para o número de arquivos abertos. O limite de arquivos abertos por processo, por exemplo, no sistema que estou usando nesse momento, é 1024. Recursos são finitos e não é desejável desperdiçá-los.
FileReader file = /* ... */;
try {
// ...
} finally {
file.close();
}
O bloco finally sempre será executado, independente se uma exceção foi lançada ou não.
Eu não quero discutir aqui, nesse momento, os impactos na performance introduzidos pelo uso de Garbage Collection, mas Garbage Collection, de fato, resolve o problema de gerenciar memória. Entretanto, esse texto é sobre gerenciamento de recursos, um problema mais geral e, por outro lado, o principal foco das técnicas de Garbage Collection é o gerenciamento de memória.
RAII
Agora que você já conhece a proposta Garbage Collection, é chegado o momento de conhecer a técnica Resource Acquisition Is Intialization, ou RAII, para economizar. A técnica, em contraste com Garbage Collection, assume que a responsabilidade de gerenciar os recursos do programa não é inteiramente do compilador/interpretador. Essa é a solução adotada por linguagens como C++.
A ideia é que todos os objetos tenham um construtor e um destrutor, sejam eles fornecidos por você ou pelo compilador. Quando um objeto entra em escopo, ele é construído, e quando sai de escopo, ele é destruído, havendo assim um gerenciamento de recursos automático e determinístico.
void f(const char *filename)
{
vector<int> array(filename, /* ... */);
fstream file;
if (!file.is_open())
return;
// Use resources
/* .. */
}
Como mostra o exemplo anterior, você não precisa criar seus próprios construtores e destrutores para se beneficiar dessa técnica. Por ser determinístico, a técnica RAII pode ser utilizada onde a técnica GC não seria, sozinha, o suficiente, o que exigiria o uso de finally. Um uso é demonstrado no exemplo a seguir:
void f()
{
lock_guard<mutex> lock(mymutex);
/* ... */
}
O código anterior mostra a simplicidade e elegância da técnica RAII. O RAII exige que você encapsule cada tipo de recurso em sua própria classe, o que poderia significar uma quantidade maior de código, porém, apesar de serem ambos comuns, aquisições de recursos são bem mais comuns que recursos personalizados que precisam de comportamento especializado. Um fato que ajuda ainda mais a aumentar a produtividade é a presença de abstrações genéricas como unique_ptr e shared_ptr que encapsulam comportamentos comuns e podem ser usadas para qualquer classe.
class MyClass
{
public:
// Constructor
MyClass()
{
}
// Destructor
~MyClass()
{
}
};
Referências
Além da experiência de memórias profundas cuja origem não é mais rastreável, esse post foi baseado, principalmente, nas aulas sobre Garbage Collection do Alex Aiken disponíveis no Coursera e em alguns artigos das edições 106 e 107 da revista Overload.
Esse texto não traz nenhuma contribuição nova para a área e o único diferencial entre ele e as referências citadas é o conteúdo em português. Entretanto, esse texto pode ajudar a divulgar conhecimento útil e alimentar a discussão sobre o tema. Além disso, até onde lembro, não é plágio se você citar as referências, então minha consciência permanece limpa, caso ela exista.
Quero aprender C++, e agora?
Ao longo dos últimos 2 anos eu conquistei um conhecimento razoável de C++ e resolvi fazer esse post para ajudar outros desenvolvedores que tenham o mesmo objetivo. O objetivo dessa postagem não é ser um “livro” que o ensine a programar, mas, sim, atuar como um “mapa” que você vai usar para encontrar os principais “livros”. Ele está organizado em 3 partes, sendo elas “por que aprender C++?”, “entendendo a linguagem” e “o percurso”. Os 3 “capítulos” são independentes e você pode lê-los em qualquer ordem.
O número de referências que usei nesse post me faz pensar naquela frase, “…sobre os ombros de gigantes“.
Por que aprender C++?
Acho que motivação é um fator importante ao adquirir novos conhecimentos, e, assim, a primeira parte desse texto é sobre motivação. Destaquei em negrito as frases que espero que você tenha em mente após terminar o texto.
Um motivo para aprender C++ é a ortogonalidade. Se a linguagem fornece algum recurso para ser utilizado, muito provavelmente permitirá que você implemente esse mesmo recurso ou um parecido usando as facilidades que ela própria fornece. Aqui, um pequeno exemplo do que seria (e é) uma quebra de ortogonalidade.
C++ é extensivamente suportada e você vai encontrar bibliotecas para resolver seus problemas nas mais diversas áreas, seja desenvolvimento de jogos, processamento de imagens, visão computacional, inteligência artificial, robótica, processamento numérico, servidor web de alta performance ou outra. Existe até suporte para novos paradigmas de programação para C++.
C++ é flexível. Se você achava que C era flexível, porque pessoas conseguiam sobreusar suas funcionalidades para implementar tratamento de exceções usando try…catch, ficaria surpreso com os códigos que a comunidade faz com C++. Se você possui necessidades incomuns, vai ficar muito contente quando examinar o design da STL, biblioteca padrão de templates de C++.
Uma característica é que a linguagem é estaticamente tipada e grande parte dos erros são capturados em tempo de compilação. Não me entenda errado, pois linguagens dinamicamente tipadas são úteis. Se uma linguagem fortemente tipada é mais adequada ao seu projeto, use C++.
Projeto com múltiplas linguagens? C++ possui conectividade com a grande maioria das linguagens existentes, chegando ao ponto onde eu tenho a ousadia de afirmar que, se uma linguagem não possui alguma forma de conectividade com C++, ela não é relevante. E mesmo nos casos extremos, você ainda terá a possibilidade de usar RPC.
Você gosta de C? Você gosta de C pelo acesso a baixo nível ou pelo desempenho? Algumas pessoas confundem desempenho e código de baixo nível. Acontece que, quando você omite os detalhes de algum comportamento, a implementação fica livre para usar a decisão mais eficiente. Um exemplo disso é a palavra-chave register, que costumava significar “coloque essa variável em um registrador” e passou a significar “por favor, coloque essa variável no lugar de acesso mais rápido”.
Outro exemplo da afirmação anterior é o canvas, um conceito usado em interfaces gráficas para permitir operações de desenho arbitrárias. Existente de forma primitiva em bibliotecas como Allegro, SDL e GTK+, e usando uma abstração de alto nível em bibliotecas como Edje e Qt. Bem, no caso da abstração de alto nível, as implementações conseguem utilizar as melhores estruturas de dados com o objetivo de diminuir o poder computacional necessário para apresentar o resultado, permitindo que, com muito menos código, você possa usar efeitos avançados de forma mais eficiente.
Um último exemplo é o sort, que em C++ é mais rápido, mesmo quando as duas implementações usam o mesmo algoritmo de ordenação. Quer usar baixo-nível para não adicionar gargalo nenhum? É a mesma coisa!
Você gosta de C e quer usar orientação a objetos? É possível usar o paradigma de orientação a objetos em C e não há problemas nisso. Há problema, entretanto, em relação a quanto de orientação a objetos você precisa. Dependendo desse fator, caso escolha C, você acabará cometendo o mesmo ato infeliz que vários outros programadores de C comentem, chegando quase ao ponto de criar uma nova linguagem em cima de C e fazendo centenas de outros programadores desprezarem o seu projeto.
Alguns outros motivos para usar C++ incluem uma carreira profissional, poder modificar o que já existe, entender melhor a tecnologia e melhorar a comunicação com pessoas da área.
Entendendo a linguagem
Um passo importante para entender uma ferramenta é entender os princípios sob os quais ela foi construída. Ao concluir esse passo, você irá adquirir a capacidade de responder sobre as características da mesma e esse “capítulo” é dedicado a lhe ajudar a entender esses princípios. Destaquei em negrito as frases que espero que você tenha em mente após terminar o texto.
Primeiramente, a linguagem não é desenvolvida por uma empresa ou um pequeno grupo com o objetivo de resolver seus próprios problemas e ignorar as consequências de suas decisões em outras áreas. A linguagem é desenvolvida por pessoas preocupadas com flexibilidade, segurança, desempenho, facilidade de uso e qualquer característica que afete sua vida, seja no desenvolvimento de um jogo de última geração, um simulador de navio, um programa de controle de avião ou qualquer outra atividade. A especificação da linguagem é publicada pela ISO e sofre um processo rigoroso antes de ser publicada. Você não ficará preso as decisões arbitrárias de uma organização com a capacidade de estregar tudo optando por C++, pois há implementações de diversas origens que lhe permitirão programar nos mais diversos locais e você não precisará pagar patentes ou algo do tipo para materializar seus conhecimentos.
Em qualquer linguagem existe a possibilidade de reutilizar pedaços de código-fonte úteis que outros programadores criaram no seu projeto. A forma mais rudimentar de reaproveitamento seria copiar e colar o código sempre que ele precisar ser reutilizado. Entretanto, as linguagens de programação sérias permitem uma forma melhor de importar código-fonte. Um dos nomes que utilizamos para citar essa características é o nome biblioteca. Geralmente uma linguagem é composta por uma biblioteca padrão e a própria linguagem. Como podemos criar nossas próprias bibliotecas, nem todas as soluções algorítmicas desenvolvidas pela espécie humana precisam fazer parte da biblioteca padrão e isso é importante.
Antes de virar código-fonte, o algoritmo existe de forma abstrata, nas nossas mentes, e a linguagem de programação escolhida impõe limitações que precisamos superar com nossas criatividades para conseguirmos o resultado (software) desejado. Quanto mais paradigmas uma linguagem suportar, menos são as limitações. Uma frase que eu li em algum lugar era algo como “o programador comum sabe o que escrever, o bom programador sabe o que reescrever”. Em resumo, reaproveitar código é o que fazemos, mas a linguagem pode ajudar (ou prejudicar) bastante essa função.
C++ nasceu como uma linguagem que adicionava orientação a objetos na linguagem C e, de fato, a saída de seu compilador, era um código-fonte C, que depois era compilado para código de máquina. Com o passar do tempo mais abstrações foram adicionadas na linguagem, até chegar ao ponto em que ela ganhou tamanha importância que começou a ter influência no desenvolvimento da linguagem da qual ela nasceu, a linguagem C. Desse primeiro pedaço, podemos entender que uma das motivações da linguagem é poder de abstração.
Com mais poder de abstração, você consegue mais produtividade e menos erros. Entretanto, se a abstração errada for escolhida, você irá perder bastante flexibilidade (dentre outras implicações) e é muito importante encontrar a decisão certa. Esse é outro princípio sob o qual a linguagem é desenvolvida. A biblioteca padrão só deve conter abstrações “corretas” e isso pode significar uma biblioteca padrão minimalista. Veja o caso de Java, por exemplo, onde a documentação contém dezenas de classes obsoletas e várias classes que tentam resolver o mesmo problema. Ou veja também o caso de Python, que se tornou “outra linguagem” na versão 3, já que perdeu compatibilidade com uma vários códigos escritos para Python 2, e até hoje, bibliotecas muito populares como Django ainda não foram portadas para Python 3. No caso de C++, demorou 13 anos para vermos outra especificação/versão e quase nenhuma funcionalidade antiga foi eliminada/desencorajada (de cabeça lembro de auto_ptr e excpetion especification).
Objective-C também nasceu na época do C++, então qual a diferença entre eles? C++ é bem mais do que apenas “C com classes” e possui diversas outras características (meta-programação com templates, sobrecarga de operadores, …), além do suporte ao paradigma OO. Mas esse ainda não é o ponto. Um dos princípios que guiou e continua guiando o desenvolvimento da linguagem C++ é que você só paga pelo que você usa. Isso influencia bastante sobre a decisão de quais são os comportamentos padrões no caso de ambiguidades. O polimorfismo, por exemplo, que não é habilitado por padrão e deve ser habilitado explicitamente para cada função-membro que deve possuir o comportamento polimórfico e que na versão nova possui um meio de restringir o mesmo para permitir que o compilador faça otimizações em certas situações.
Isso é tudo que vou citar nesse “capítulo”, mas vale ressaltar que a linguagem é uma evolução da linguagem C e grande parte do que vale para C, também vale para C++, como controle (acesso direto ao hardware), portabilidade e eficiência.
O percurso!
Esse é o “capítulo” que você usa para seguir até os locais que distribuem conhecimento em C++. E não esqueça de usar o seu conhecimento para o bem. Sempre que desanimar, veja uma foto minha e isso irá lhe inspirar.
Uma pequena lista de hábitos importantes que você deve desenvolver segue:
- Você deve usar a linguagem. Use C++ em seus próximos projetos. Depois convoque seus amigos para ajudá-lo. Quem sabe não acaba criando um projeto de sucesso?
- Ensine C++. Crie um blog, faça apresentações, responda perguntas ou faça um livro. Escolha o caminho que lhe agradar mais, pois essa tarefa irá exigir dedicação, mas, irá lhe fornecer, em troca, bastante conhecimento, caso se dedique da forma correta.
- Siga outros programadores de C++. Provavelmente eles terão algo a lhe ensinar e você se sentirá mais motivado.
- Perca o preconceito de aprender inglês. É importante eliminar essa barreira que lhe impede de entrar em contato com um número maior de pessoas interessantes.
E como dicas são indignas da filosofia Chuck Norris de vida nem todo mundo gosta de dicas, deixo a lista de dicas separadas da lista anterior:
- Crie uma pasta para programas simples (converter temperatura, mostrar código ASCII de um caractere, somar vários números…) que você irá criar durante seu aprendizado
- Aprenda a utilizar um depurador, pois essa ferramenta lhe auxiliará não somente na tarefa de encontrar erros, mas também a compreender melhor o comportamento do seu código-fonte
- É importante que você não utilize uma IDE no começo, pois assim terá uma compreensão maior do que acontece quando você compila um projeto.
- Use um VCS pelo menos para um de seus projetos, pois esse é o modo de trabalhar em grupo. Você pode usar o Google code para hospedar seus projetos open source.
- Documente pelo menos um de seus projetos, pois essa é uma experiência importante
- Arquitete pelo menos um de seus projetos, pois essa é outra experiência importante
- Crie testes para pelo menos um de seus projetos, pois essa é mais outra experiência importante
- Não siga todas as dicas anteriores simultaneamente, pois isso lhe fará perder tempo
- Use build systems multiplataformas em seus projetos que tem o objetivo de ser mais do que “apenas um projeto de aprendizado”
- Em algum momento você vai precisar desenvolver códigos mais complexos e a biblioteca-padrão não mais irá lhe satisfazer. É para isso que bibliotecas como a boost e a Qt estão aí.
As minhas fontes de conhecimento de C++ estão organizadas em duas categorias principais. A categoria “morta”, que é composta por fontes que, caso você tenha memória boa o suficiente, só precisa consultar uma vez, e a categoria “viva”, composta por fontes que estão constantemente evoluindo. Considero importante manter um contanto com a comunidade e evoluir com ela e isso é para o que as fontes “vivas” existem.
A fonte morta
Livros são uma ótima forma de se aprender a programar, pois eles costumam agrupar os principais conhecimentos da linguagem, códigos de exemplo, exercícios, curiosidades interessantes e perguntas intrigantes. Entretanto, a linguagem que um autor utiliza para passar conhecimento pode ser boa para mim e ruim para você, então é importante que você não compre um livro antes de conhecê-lo. Antes da compra, use bibliotecas ou mesmo baixe alguns capítulos do mesmo. Quando chegar o momento em que um livro não mais lhe serve, abandone-o e tente outro.
Como C++ é um superconjunto da linguagem C, alguns livros sobre C também podem ser utilizados. O problema com essa abordagem é que eles podem lhe fazer perder tempo utilizando técnicas obsoletas e que em C++ há um jeito mais fácil de fazer que ainda fornece mais segurança e desempenho.
Alguns materiais memoráveis com os quais tive contato (ou não):
- cppreference.com: Durante seu aprendizado você vai precisar de uma referência a consultar. Essa é a melhor referência com a qual já tive contato.
- Curso de Linguagem C: Eu aprendi a programar com essa apostila. Uma característica notável da mesma é que ela esconde poucos detalhes da linguagem, deixando menos dúvidas na sua mente.
- C++ Black Book: Esse não é um livro tão bom, mas permitiu-me ter conhecimento que não consegui de outras formas, principalmente, porque ele tem o que falta em alguns livros, exemplos de código-fonte.
- Accelerated C++: Practical Programming by Example: Um livro com uma abordagem de ensino “inovadora”, ensinando a usar facilidades de alto-nível antes de ensinar como elas funcionam.
- Programming – Principles and Practice Using C++: Esse é um dos melhores livros ensinando programação dos quais eu tive acesso (obrigado ao professor Alcino da UFAL, por ter me emprestado ele por um tempo). Acabei encontrando esse livro num momento em que estava insatisfeito com a falta de exercícios propostos nos livros de programação.
- The C++ Programming Language: Apesar de ser um livro que consegue reunir explicações para boa parte da linguagem, ele não possui uma ordem muito didática, tornando-se uma péssima escolha para uma primeira leitura.
- C Completo e Total: Aborda
um pouco de “magia negra”tópicos avançados na linguagem, sendo uma boa escolha depois que você tiver dominado o básico da linguagem e estiver à procura de novos desafios. - https://en.wikipedia.org/wiki/C++11: Eu sei que nenhuma página da wikipedia está livre de futuras revisões, mas essa página, em especial, está bem estável e é uma ótima leitura para entender as mudanças do novo padrão de C++.
- Modern C++ Design: Esse é um livro que eu ainda não tive a chance de ler, porém parece ser bem popular entre os
magosdesenvolvedores da boost e as palestras do Andrei Alexandrescu possuem informações interessantes. - Effective C++: Outro livro que ainda não tive a oportunidade de ler, mas também possui vários indícios que o faz parecer promissor.
- Guia Foca GNU/Linux: Apesar de não ter relação direta com C++, foi com essa apostila que aprendi bastante sobre computadores e por isso recebe menção honrosa.
A fonte viva
É importante participar de uma comunidade durante o processo (eterno?) de aprendizado. Sem mais enrolação, segue uma lista:
- Lista de C & C++ Brasil: Dentre as dezenas de listas de emails das quais faço parte, essa é uma das mais respeitosas. Vez ou outra você irá encontrar algo de interessante na lista, além de ter um lugar para discutir sobre C++.
- reddit: Sempre disponibilizando um link a mais para me ajudar a ser um programador melhor.
- The home of Standard C++ on the web: É o mais próximo que temos de “site oficial” da linguagem.
- Channel 9: Um lugar onde você pode assistir vídeos de eventos de C++. Há material bastante interessante que você não deveria subestimar. Usuários do reddit costumam postar os links para novos vídeos que aparecem aqui.
- cppreference.com: Esse é um link tão importante que merece menção dupla no mesmo post.
E assim como eu recomendei que você seguisse outros programadores de C++, eu também o faço:
- Murilo
- A day in the life: apesar do nome, esse blog é brasileiro e escrito em português.
- Master of my Domain: apesar do nome, esse blog também é brasileiro e escrito em português. Seu autor não é programador de C++, mas aborda tópicos interessantes e recebe menção honrosa, mesmo não tendo publicado nada há muito tempo.
- CodeCereal
- C++Next
- A Sense of Design
- Brandon’s Thoughts
- CppCMS Blog
- Jeremy Pack’s Coding Blog
- LLVM Project Blog
- Multi-paradigm
- ACCU Overload journals: que não tem um feed RSS oficial que funcione e por isso eu
não acompanhouso esse JollyRog3r’s Garage, que prometeu voltar a postar (e sobre C++), e até lá vai ter a citação riscada
Os números de 2012
Os duendes de estatísticas do WordPress.com prepararam um relatório para o ano de 2012 deste blog.
Aqui está um resumo:
600 pessoas chegaram ao topo do Monte Everest em 2012. Este blog tem cerca de 4.500 visualizações em 2012. Se cada pessoa que chegou ao topo do Monte Everest visitasse este blog, levaria 8 anos para ter este tanto de visitação.












Comentários