Carregando...
 

Blog do curso de ADS

C++ para programadores Java

Sandro Santos Andrade Sábado Dezembro 8, 2018

Muitos por aí­ afirmam que a complexidade da linguagem C++ é o principal motivo para a sua não utilização. Embora seja, de fato, uma linguagem que exija um pouco mais de atenção e conhecimento por parte do programador, acredito que algumas poucas diretrizes básicas já sejam capazes de eliminar grande parte da complexidade de aprendizado da linguagem, principalmente se você já possui alguma experiência, por exemplo, com a linguagem Java. O objetivo deste tutorial é apresentar as principais diferenças entre as linguagens C++ e Java, de modo a reduzir a curva de aprendizado e o receio da utilização do C++ nos projetos do dia-a-dia. Não tenho a intenção, entretanto, de apresentar uma lista exaustiva das diferenças entre o Java e C++. O ganho em desempenho, excelente portabilidade e recursos modernos quando aliados, por exemplo, a bibliotecas como o Qt e às novas funcionalidades introduzidas no C++11/14/17, fazem do C++ uma linguagem bastante atual.

Vamos lá ! Neste tutorial, códigos C++ aparecerão sempre com fundo preto, enquanto códigos Java aparecerão com fundo azul.

Alocação de objetos

O Java trabalha sempre com referências e alocação dinâmica. No código abaixo:

Aluno a1 = new Aluno();
Aluno a2 = new Aluno("Joao");

a1 e a2 são referências que irão apontar para dois objetos do tipo Aluno, recém-alocados dinamicamente (o primeiro usa o construtor sem parâmetros e o segundo um construtor que recebe o nome do aluno como parâmetro). O Java automaticamente implementa contagem de referências e garbage collection. Portanto, estes objetos serão automaticamente marcados para destruição (não necessariamente imediatamente destruí­dos) quando o número de referências que apontam para cada um deles passar a ser zero. O C++, por sua vez, suporta duas formas de alocação: estática e dinâmica. A forma correspondente, em C++, à alocação realizada no código Java acima (alocação dinâmica) é:

Aluno *a1 = new Aluno; // O C++ não requer os parêntenses quando
                       // chamando o construtor sem parâmetros.
Aluno *a2 = new Aluno("Joao");
// Uso dos objetos
delete a1;
delete a2;

Neste caso, a1 e a2 são ponteiros (correspondentes às referências do Java não confundir com passagem de parâmetros por referência, explicado a seguir) para objetos do tipo Aluno. No momento em que estes objetos não forem mais necessários, o programador deve explicitamente destruí­-los com o comando delete (ou, preferencialmente, confiar no uso de smart pointers para isso). Lembrando que a forma correta de liberação de memória de arrays alocados dinamicamente é:

Aluno *array = new Aluno[size];
// Uso dos objetos
delete[] array; // Em vez de "delete array;" que libera
                // somente o 1o elemento do array

A segunda forma de alocação de objetos no C++ é a alocação estática:

Aluno a1;
{
    Aluno a2("Joao");
}
...

Neste caso, a1 e a2 são objetos do tipo Aluno em contraponto a ponteiros para objetos do tipo Aluno, como no exemplo anterior). Objetos alocados estaticamente são automaticamente destruí­dos quando eles saem do escopo no qual foram criados, ou seja, no exemplo acima o objeto a2 será automaticamente destruí­do na linha 4 (final do escopo anônimo onde a2  foi criado).

Passagem de parâmetros

Como consequência das diferentes formas de acesso a objetos no C++ e no Java, tais linguagens também possuem diferentes formas de passagem de parâmetros. No Java, a única forma de passagem de parâmetro suportada é por cópia (que, na prática, acaba sendo passagem de objetos por referência):

public void matricularAluno(Aluno a)
{
    // Código de matricula
}

public static void main(String []args)
{
    ...
    if (...) {
        Aluno a1 = new Aluno();
        matricularAluno(a1);
    }
    ...
}

No código acima, a linha 10 cria um novo objeto, cria a referência a1 e a faz apontar para o objeto recém-criado (seu contador de referências é inicializado com 1). A linha 11 passa esta referência, por valor (cópia), para o método matricularAluno(), que cria uma nova referência a apontando para o mesmo objeto (o contador de referências deste objeto passa a ser 2). Na linha 4, o método finaliza, destrói a referência a e o contador de referências do objeto Aluno volta a ser 1. Finalmente, na linha 12, a referência a1 é destruí­da, o contador de referências do objeto passa a ser 0 e ele passa a estar disponí­vel para que o coletor de lixo o destrua em algum momento futuro. Passagem por valor pode ser simulada no Java através do uso do método clone(), presente em todos os objetos (herdado da classe Object).

O C++, entretanto, suporta dois tipos de passagem de parâmetros (por valor ou por referência). NOTA: a linguagem C suporta somente passagem por valor (métodos matricularAluno1() e matricularAluno2(), abaixo).

// Passagem por VALOR
void matricularAluno1(Aluno a)
{
    // Modificações em a NÃO ALTERAM o objeto a1 original            
    a.setStatus(Aluno.Matriculado);
}

// Passagem DE PONTEIRO POR VALOR (simulação de passagem por referência)
void matricularAluno2(Aluno *a)
{
    // Modificações em a ALTERAM o objeto a1 original
    a→setStatus(Aluno.Matriculado);
    // Note acima que o C++ possui dois operadores de
    // acesso a features públicas (atributos e métodos):
    // 1) operator ".": utilizado a partir de um objeto ou referência
    // 2) operator "→": utilizado a partir de um ponteiro
}

// Passagem REAL POR REFERÊNCIA
void matricularAluno3(Aluno &a)
{
    // Modificações em a ALTERAM o objeto a1 original    
    a.setStatus(Aluno.Matriculado);
}

int main(int argc, char *argv[])
{
    Aluno a1;
    matricularAluno1(a1);
    matricularAluno2(&a1);
    matricularAluno3(a1);
}

No método matricularAluno1() o objeto é passado por valor, ou seja, a é um objeto que é criado a partir de uma cópia do objeto original a1 (mais sobre copy constructors a seguir). O método matricularAluno2() também apresenta uma passagem de parâmetro por valor, só que está sendo realizada agora uma cópia do ponteiro e não do objeto (esta é a forma correspondente ao funcionamento normal do Java). Note como é necessário passar o endereço do objeto na linha 30, recebê-lo como um ponteiro na linha 9 e utilizá-lo como tal na linha 12. Já o método matricularAluno3() apresenta uma passagem real por referência (utilizando o operador & não confundir com o mesmo operador utilizado como endereço-de, na linha 30).

Inicialização de atributos

O Java possui duas formas para inicialização de atributos. A mais clássica, utilizando o construtor:

class Aluno
{
    public Aluno()
    {
        status = "Matriculado";
        faltasRestantes = 27;
    }
    private String status;
    int faltasRestantes;
}

E a mais "esotérica" smile, direto na declaração dos atributos:

class Aluno
{
    public Aluno()
    {
    }
    private String status = "Matriculado";
    int faltasRestantes = 27;
}

No C++, os atributos podem ter os seus valores atribuí­dos no construtor:

class Aluno
{
public:
    Aluno()
    {
        _status = "Matriculado";
        _faltasRestantes = 27;
    }
private:
    string _status;
    int _faltasRestantes;
};

ou inicializados na lista de inicialização da classe (método recomendado):

class Aluno
{
public:
    Aluno() : _status("Matriculado"), _faltasRestantes(27) // LISTA DE INICIALIZAÇÃO
    {
    }
private:
    string _status;
    int _faltasRestantes;
};

Quando os atributos da classe são objetos grandes, a atribuição dos seus valores iniciais no corpo do construtor pode trazer danos ao desempenho geral do software. A razão é: realizar atribuições no corpo do construtor implica na execução do construtor, seguida da execução subsequente do operator= do objeto sendo inicializado. Por outro lado, a lista de inicialização é o momento correto para informar como os atributos-objeto deverão ser inicializados, levando à execução apenas do construtor. Embora essa diferença de desempenho não seja tão substancial em atributos que são de tipos primitivos, é boa prática a utilização da lista de inicialização sempre que possí­vel, mesmo para atributos de tipos primitivos.

Copy-Constructor e operator=

Assim como no Java, o C++ cria automaticamente um construtor default, de implementação vazia, caso o programador não implemente explicitamente nenhum construtor para a classe. Diferente do Java, e por suportar passagem por valor e atribuição entre objetos (em contraponto a atribuição entre referências/ponteiros), o C++ também cria automaticamente implementações default para dois métodos importantes: o copy-constructor e o operator=. Veja os exemplos abaixo:

Aluno a1;       // Construtor sem parâmetros é chamado
Aluno a2 = a1;  // INICIALIZAÇÃO: copy-constructor é chamado
                // Semelhante a: Aluno a2(a1);
Aluno a3;       // Construtor sem parâmetros é chamado
a3 = a2;        // ATRIBUIÇÃO: operator= é chamado

Na linha 1 é criada uma instância a1 da classe Aluno, através da invocação do construtor sem parâmetros (implementado pelo programador ou o construtor default, disponibilizado pela linguagem). Já na linha 2, deseja-se criar o objeto a2 e inicializá-lo a partir do objeto a1. Neste momento, um tipo especial de construtor é invocado, o copy-constructor, que tem sempre a seguinte assinatura:

Aluno(const Aluno &other)
{
    // ...
}

Se você está passando objetos por valor no C++ e não implementou explicitamente o copy-constructor significa que você está utilizado o copy-constructor default, disponibilizado pela própria linguagem. E o que este copy-constructor default faz? Diferente do construtor default que não faz nada, o copy-constructor default realiza uma cópia membro-a-membro (atributo-a-atributo) de objetos. Ou seja:

class Aluno
{
...
private:
    char _idade; // char para idade ? ;-) Você conhece
                 // alguém com mais de 255 anos ?
                 // Ou -10 anos ? ;)
    char _sexo;
};               // Note que o C++ requer um '';'' aqui, o Java não :)
...
int main(int argc, char *argv[])
{
    Aluno a1(20, 'M');
    Aluno a2 = a1;
}

O copy-constructor default executado na linha 14 copia os atributos _idade e _sexo de a1 para a2. Por qual razão você teria necessidade de explicitamente implementar um copy-constructor? Resposta: quando a classe contiver dados exógenos e você precisar de deep-copy. Calma, é muito simples smile. Suponha que a classe Aluno fosse:

class Aluno
{
...
private:
    char _idade;
    char _sexo;
    Matricula *_matricula;
};
...
int main(int argc, char *argv[])
{
    Aluno a1(20, ''M'');
    Aluno a2 = a1;
}

Como o copy-constructor default realiza somente cópia membro-a-membro, seria realizada uma cópia do ponteiro para a matrí­cula do aluno, fazendo com que a2 e a1 apontassem para o mesmo objeto de matrí­cula. Supondo que a semântica da associação Aluno-Matricula é uma composição (ciclo de vida da Matricula condicionado ao ciclo de vida do Aluno e uso exclusivo de uma Matricula por um único Aluno), então o copy-constructor default não te dá uma implementação correta. Por exemplo, ao destruir o aluno a2 sua matrí­cula também deve ser destruí­da, porém como esta matrí­cula é a mesma de a1 (porque o copy-constructor default implementa shallow-copy) então a1 ficaria com um ponteiro para uma matrí­cula que já foi destruí­da (dangling pointer), o que certamente provocaria algum crash em run-time. Para implementar o deep-copy, o copy-constructor deve ser explicitamente implementado pelo programador:

Aluno(const Aluno &other)
    : _idade(other._idade),
      _sexo(other._sexo),
      _matricula(new Matricula(other._matricula))
      // O copy-constructor de Matricula é chamado acima.
      // Se Matricula não contém dados exógenos ou a semântica
      // das associações permitir o compartilhamento então o
      // copy-constructor default de Matricula é suficiente.
      // Caso contrário, é necessário implementar um copy-
      // constructor customizado.
{
}

Além das inicializações explicitas apresentadas acima, o copy-constructor também é invocado nas passagens e retornos de objetos por valor:

Aluno parceiroNaEquipe(Aluno a)
{
    // b = obtém o aluno parceiro de a
    return b;
}

int main(int argc, char *argv[])
{
    Aluno a1; 
    Aluno b1 = parceiroNaEquipe(a1);
}

Entretanto, o copy-constructor não é invocado nas passagens de parâmetros por referência:

Aluno parceiroNaEquipe(Aluno &a)
{
    // b = obtém o aluno parceiro de a
    return b;
}

int main(int argc, char *argv[])
{
    Aluno a1; 
    Aluno b1 = parceiroNaEquipe(a1);
}

Nenhuma invocação de copy-constructor acontece no código acima, diminuindo consideravelmente a movimentação de dados na memória e, portanto, melhorando o desempenho. Tudo o que foi dito acima para o copy-constructor é também válido para o operator=, que possui a seguinte assinatura:

Aluno& operator=(const Aluno &rhs)
{
    // Implemente seu deep-copy aqui
    return *this;
}

DICA1: se você quiser proibir de uma vez por todas a passagem por valor de objetos de uma determinada classe, basta criar o copy-constructor e o operator= com implementações vazias e declará-los como private smile.

DICA2: o Qt implementa a técnica de implicit sharing/copy-on-write para minimizar a cópia de dados, mesmo na passagem de objetos por valor.

Operador de escopo

Classes dentro de packages são referenciadas, no Java, utilizando o "." como separador.

package pac1;

class Aluno
{
}
...
package pac2;

class Foo
{
    public static void main(String []args)
    {
        pac1.Aluno a = new pac1.Aluno();
    }
}

Já o C++ utiliza o "::" como separador:

namespace Nam1
{
    class Aluno
    {
    };
};
...
int main(int argc, char *argv[])
{
    Nam1::Aluno *a = new Nam1::Aluno;
}

Operadores de Visibilidade

Enquanto o Java exige que você indique, em cada linha, o operador de visibilidade sendo utilizado:

public class Aluno
{
    public Aluno();
    public void realizarProva();

    private String nome;
    private int idade;
}

O C++ trabalha com seções:

class Aluno
{
public:  // INICIO DA SEÇÃO PÚBLICA
    Aluno();
    void realizarProva();
private: // INICIO DA SEÇÃO PRIVADA
    string _nome;
    int _idade;
public:  // POSSO REABRIR UMA SEÇÃO SE EU QUISER
};

Adicionalmente, o Java exije que um arquivo Foo.java contenha uma classe pública denominada Foo. O arquivo pode conter outras classes, desde que sejam privadas. O C++ não possui esta exigência de equiparação "nome de arquivo  nome de classe", embora seja uma boa prática a definição de uma única classe por arquivo, mantendo os nomes equivalentes. No Java, atributos/métodos protected são visí­veis em sub-classes e em classes do mesmo pacote. O Java possui ainda um operador default de visibilidade de pacote (quando nem public, nem private, nem protected são utilizados), que indica que o atributo/método é visí­vel por outras classes do mesmo pacote, porém não é visí­vel em sub-classes. O C++ não possui o conceito de visibilidade de pacote, mas pode ser simulado utilizado declarações friend.

Interfaces

O Java possui uma palavra reservada especí­fica para a declaração de Interfaces:

public interface IDatabase
{
    public boolean connect(String user, String md5Password);
    public boolean disconnect();
};

... enquanto o C++ assume que uma Interface nada mais é que  uma classe somente com métodos abstratos (chamados, no C++, de métodos virtuais puros):

class IDatabase
{
public:
    virtual bool connect(string user, string md5Password) = 0;
    virtual bool disconnect() = 0;
};

No Java, somente dois tipos diferentes de métodos existem: os métodos concretos (que possuem corpo e podem estar envolvidos ou não em ligação dinâmica) e os métodos abstratos (declarados em uma interface ou como abstract em classes abstratas). Já o C++ possui três categorias de métodos: métodos concretos não-passí­veis de ligação dinâmica, métodos concretos passí­veis de ligação dinâmica (métodos virtuais) e métodos abstratos (métodos virtuais puros). Métodos concretos não-passí­veis de ligação dinâmica são métodos convencionais que, por não estarem envolvidos em ligação dinâmica, podem ser superotimizados pelo compilador:

class Aluno
{
public:
    void realizarProva(); // Método concreto não-passí­vel de ligação dinâmica
};

Os métodos virtuais puros são geralmente utilizados na definição de interfaces, conforme apresentado na interface IDatabase acima. Já os métodos virtuais (não-puros) são utilizados em implementações de interfaces e são frequentemente utilizados em ligações dinâmicas:

class PostgreSQL : public IDatabase
{
public:
    virtual bool connect(string user, string md5Password)  // Note a remoção
    {                                                      // do "= 0";
        // Implementação
    }
    virtual bool disconnect()
    {
        // Implementação
    }
};

 

Herança

O Java não suporta herança múltipla, embora seja possí­vel a implementação de múltiplas interfaces em um mesmo objeto:

public class PostgreSQL extends CommonDatabase implements IDatabase, ILoggable
{
    public boolean connect(String user, String md5Password)
    {
        // Implementação
    }
    public boolean disconnect()
    {
        // Implementação
    }
    public boolean log(String message)
    {
        // Implementação
    }
}

Note como o Java utiliza diferentes palavras-reservadas, extends e implements para denotar herança de implementação e herança de interface, respectivamente. O C++, entretanto, não possui diferença sintática entre herança de implementação e herança de interface:

class PostgreSQL : public CommonDatabase, public IDatabase, public ILoggable
{
public:
    virtual bool connect(string user, string md5Password)
    {
        // Implementação
    }
    virtual bool disconnect()
    {
        // Implementação
    }
    virtual bool log(string message)
    {
        // Implementação
    }
};

Note como a mesma sintaxe "public"  é utilizada. O tipo de herança é "inferido" a partir dos tipos de métodos (convencionais, virtuais ou virtuais puros) declarados nas super-classes/super-interfaces. O C++ pode ainda trabalhar com herança privada e herança protegida. Não serão tratadas aqui por serem utilizadas somente em poucos casos particulares. O leitor interessado pode consultar o Parashift C++ FAQ.

Ligação dinâmica e polimorfismo

Enquanto no Java, uma ligação dinâmica pode acontecer naturalmente em uma hierarquia com sobreposições de métodos, no C++ é necessário preparar explicitamente o polimorfismo. Considere o código abaixo:

public IDatabase
{
public:
    virtual boolean connect(string user, string md5Password)
    {
        // Implementação do método connect
    }
   
    boolean disconnect()
    {
        // Sobreposição do método disconnect
    }
};
...
void connect1(IDatabase database)
{
    database.connect(...);
}
...
void connect2(IDatabase *database)
{
    database->connect(...);
}
...
void connect3(IDatabase &database)
{
    database.connect(...);
}
...
void disconnect(IDatabase &database)
{
    database.connect(...);
}
...
int main(int argc, char *argv[])
{
    IDatabase *db = new PostgreSQL;
    connect1(*db);      // Não funciona
    connect2(db);       // Funciona
    connect3(*db);      // Também funciona
    disconnect(*db);    // Não funciona
}

A regra é bem clara, para que uma ligação dinâmica aconteça em C++ duas coisas são necesárias: 1) todos os métodos na hierarquia, do tipo da referência até o tipo real do objeto apontado, devem ser virtuais; e 2) a invocação esteja sendo feito a partir de um ponteiro ou referência. Ou seja, no exemplo acima, a invocação na linha 47 não executa o connect1() da classe PostgreSQL por violar a condição 2) na linha 26: a invocação do método connect1() está sendo realizada a partir de um objeto (database). A invocação da linha 50 também não funciona porque embora a chamada seja feita com base em uma referência (database, declarada na linha 39), o método disconnect() não é um método virtual. Apenas as invocações das linhas 48 e 49 atendem às duas condições.

Uma última ressalva: destrutores de classes participantes de hierarquias com ligação dinâmica devem ser sempre declarados como virtuais. Esteja avisado, coisas estranhas podem acontecer caso contrário smile Ele simplesmente te faz ser "í­ntimo" de uma classe. Em vez de se referir a ela utilizando seu nome completo (por exemplo: br.edu.ifba.Aluno), o "import br.edu.ifba.Aluno;" te habilita a acessá-la utilizando apenas o nome "í­ntimo": Aluno. Quem importa a classe na verdade é a colocação, do arquivo .jar do qual ela faz parte, no CLASSPATH. O equivalente ao import do Java no C++ é o operador using e o equivalente à colocação do .jar no CLASSPATH do Java é a diretiva #include do C++:

#include "pessoa.h"
#include "matricula.h"
#include "professor.h"
#include "nota.h"
#include "avaliacao.h"

class Aluno : public Pessoa
{
public:
    void registrarNota(Nota nota);
    void solicitar2aChamada(const Avaliacao &avaliacao);
private:
    Matricula _matricula;     // Fácil para implementar composições
    Professor *_orientador;   // Melhor para implementar agregações
};

Embora o código acima compile perfeitamente, alguns dos #includes acima não são realmente necessários. A regra é: o #include só é realmente necessário quando: 1) a classe do #include for uma super-classe; ou 2) a classe do #include for utilizada como atributo-objeto ou passagem de parâmetro por valor. Note acima que Matricula é utilizada como atributo-objeto, enquanto Professor é utilizado como atributo-ponteiro. Significa, portanto, que o #include da classe Professor não é necessário, ele pode ser sustituí­do por um forward declaration. O mesmo vale para Avaliação, que é utilizada como parâmetro, mas não com passagem por valor e sim por referência. O forward declaration tem a forma:

class ;

ou:

namespace  {namespace  {    ...    class ;    ...}}

para classes declaradas em namespaces. A grande vantagem do forward declaration é a considerável redução do tempo de compilação e adiamento das dependências somente para o momento de linkagem. Veja como fica o código acima após a substituição dos #includes desnecessários por forward declarations:

#include "pessoa.h"
#include "matricula.h"
#include "nota.h"

class Avaliacao;
class Professor;

class Aluno : public Pessoa
{
public:
    void registrarNota(Nota nota);
    void solicitar2aChamada(const Avaliacao &avaliacao);
private:
    Matricula _matricula;     // Fácil para implementar composições
    Professor *_orientador;   // Melhor para implementar agregações
};

Ou seja, mais um motivo para você sempre passar objetos por referência (constante) sempre que possí­vel smile). No Java, o método main é um método estático de uma das classes do sistema:

public class Aluno
{
    public static void main(String args[])
    {
        // Implementação
    }
};

No Java, um grupo de classes compiladas (arquivos .class) podem ser compactadas em um arquivo .jar. Se pelo menos uma das classes deste .jar contiver o método main, então o .jar representa uma aplicação Java. Caso contrário, o .jar é uma biblioteca. Aplicações C++ devem conter uma função (note que não a denominamos mais "método") main, presente no namespace global, fora de qualquer classe do sistema:

int main (int argc, char *argv[])
{
    // Implementação
    // retorne seu exit-code
};

Um grupo de classes C++ compiladas (arquivos .o), sem nenhuma definição da função main, representa uma biblioteca e podem ser agrupadas em um arquivo .a (biblioteca estática) ou .so/.DLL (biblioteca dinâmica).

Estruturação de código e Compilação

No Java, os métodos são declarados e definidos na mesma "unidade de compilação":

// Arquivo Aluno.java
public class Aluno
{
    public void realizarProva() // DECLARAÇÃO
    {
        // DEFINIÇÃO  
    }
};

Já no C++, cada classe requer a criação de dois arquivos:

// Arquivo de declaração: aluno.h
#ifndef _ALUNO_H_    // "Guarda" para impedir a inclusão múltipla
                     // deste header
#define _ALUNO_H_

class Aluno
{
public:
    Aluno();              // SOMENTE A ASSINATURA
    void realizarProva(); // SOMENTE A ASSINATURA
private:
    string _status;
    int _faltasRestantes;
};

#endif // _ALUNO_F_

 

// Arquivo de definição: aluno.cpp
#include "aluno.h" // Inclusão do arquivo header correspondente

Aluno::Aluno()   // Note o uso do operador de escopo
                 // Não queremos implementar a função Aluno() no
                 // namespace global, mas sim aquela presente no
                 // namespace/classe Aluno
    : _status("Matriculado"), _faltasRestantes(27) // LISTA DE INICIALIZAÇÃO
{
}

void Aluno::realizarProva()
{
    // Implementação
}

A separação é necessária em função das duas atividades básicas realizadas, pelo compilador, na construção de aplicações C++: a compilação e a linkagem. Na compilação, somente os headers são necessários para saber o quê as classes fazem. A linkagem é responsável por "ligar" a parte que diz como as classes implementam o que elas dizem que fazem. Isso permite a compilação antecipada de bibliotecas, para que você não precise recompilá-las sempre que compilar sua aplicação. A aplicação é compilada utilizando somente os headers da biblioteca e faz-se a linkagem com a biblioteca já compilada, diminuindo consideravelmente o tempo total de build. A separação em arquivos .h/.cpp permite ainda a distribuição de bibliotecas proprietárias (sem disponibilização de código-fonte), ao publicar somente os headers e os arquivos .a/.so contendo a biblioteca já compilada (é claro que você é um menino/menina bonzinho/boazinha e trabalha sempre com open-source. smile

Duas exceções a esta regra: 1) na declarações de funções inline (diretriz que sugere que o compilador replique o código da função em cada local onde é chamada, melhorando o desempenho através da não-execução da função); e 2) na implementação de funções templated.

Conclusão

Bom, a intenção aqui não é disponibilizar um curso completo de C++, mas sim apresentar um meio rápido de aprendizado do "jeito C++" de programar por programadores com algum background prévio da linguagem Java.

Comentários e sugestões são sempre bem vindos ! smile