Dúvida gerais em C++

Pessoas, se puderem me esclarecer mais essa:

[code]#ifndef STATE_H
#define STATE_H

#include

using namespace std;

class State
{
public:
State(string);
virtual ~State();
protected:
private:
};

#endif // STATE_H
[/code]

[code]#include “Helper.h”

Helper::Helper(const State & state) {
this->state = state;
}

Helper::~Helper() {
//dtor
}
[/code]

É como se eu estivesse tentando instanciar State com o construtor padrão.

Primeiro algumas correções:

#ifndef STATE_H
#define STATE_H

#include <string>

using namespace std;

class State
{
    public:
        explicit State(string);
        virtual ~State();
    protected:
    private:
};

#endif // STATE_H

Aqui está o problema. Você não está usando a lista de inicialização, e sim atribuindo com = dentro do construtor. Com isso, o C++ tenta primeiro inicializar a referência com o construtor padrão. E depois (se você tivesse um construtor padrão) te daria erro dentro do construtor dizendo que você não pode alterar o valor da referência.

[code]#include "Helper.h"
Helper::Helper(const State& _state) : state(_state) {
}

Helper::~Helper() {
}
[/code]

Use sempre a lista de inicialização para declarar seus atributos, e não o corpo do construtor. O corpo deve ser usado só para inicializar atributos complexos, que tem valores carregados de arquivos, por exemplo.

Pelo que procurei agora, se o construtor tiver só 1 parâmetro e não for declarado como explicit, ele sempre tenta converter o parâmetro para o mesmo tipo da classe do construtor?

Sobre a lista de inicialização, qual a explicação para essa (aparente) loucura?
Com a atribuição com =, ele primeiro tentou instanciar o atributo de Helper para depois atribuir ao parâmetro? Por quê?

E se não fosse uma simples atribuição? Se tivesse uma validação antes, por exemplo?

Se o construtor não for explicit, ele automaticamente será chamado. Por exemplo, você poderia fazer isso aqui:
State x = “Novo estado”;

E o C++ automaticamente converteria para isso aqui:
State x = new State(novoEstado);

Já que você tem um construtor que aceita um std::string. O mesmo vale pra métodos que aceitem um State como parâmetro.

Como isso aí gera um código sujeito a erros, você só deve declarar o construtor sem explicit caso sua classe efetivamente represente algo que deva ser implicitamente convertido. Por exemplo, a classe BigDecimal do Java poderia se beneficiar desse recurso.

[quote=Schuenemann]Sobre a lista de inicialização, qual a explicação para essa (aparente) loucura?
Com a atribuição com =, ele primeiro tentou instanciar o atributo de Helper para depois atribuir ao parâmetro? Por quê?
E se não fosse uma simples atribuição? Se tivesse uma validação antes, por exemplo?[/quote]

Por que é assim que o C++ funciona. Antes de começar o construtor, ele faz a inicialização padrão de todos os atributos da classe. Quem comanda essa inicialização padrão é a lista de inicialização.
O java, no fundo, faz exatamente a mesma coisa. Tanto que quando você roda o construtor, todos os atributos valem 0, e todas as referências valem null, ou seja, uma inicialização padrão foi executada.

A diferença é que a lista de inicialização te dá a oportunidade de alterar essa inicialização padrão, poupando alguns milisegundos de processamento.

O Java não suporta o conceito de referência, como o C++, e não suporta também o conceito de objetos por valor. Nesses casos, você tem que tomar especial cuidado, pois na inicialização padrão o C++ irá tentar criar um objeto novo, usando o construtor padrão. A lista de inicialização se torna importante, pois a construção de um objeto, diferente da de um tipo primitivo, pode ser custosa.

Se você precisar validar valores de atributos, faça isso dentro do corpo do construtor. A única diferença é que os atributos não irão valer 0, pois os valores já estarão atribuídos, graças a lista de inicialização. Lembre-se que referências em C++ obrigatoriamente devem conter um valor de objeto, pois não podem ser nulas. No caso de ponteiros, é uma prática comum inicializa-los com null na lista de inicialização, fazer as validações de atributos no construtor, para tentar evitar dar o new, como o java faz.

É, faz sentido.

Sobre essa parte:

É melhor eu ter atributos ponteiros ao invés de objetos? Ponteiro para State ao invés de State propriamente dito. Senão, não vai ter jeito de evitar a inicialização padrão, se não fosse uma simples atribuição.

Aproveitando o abuso, me apareceu outro problema. Eu preciso de um atributo arquivo:

[code]#ifndef STATE_H
#define STATE_H

#include
#include

using namespace std;

class State {
public:
explicit State(string);
virtual ~State();
protected:
private:
fstream file;
void readData();
};

#endif // STATE_H
[/code]
Dá um erro dentro de ios_base.h:

Eu já tinha trabalhado com fstream antes dentro de um método:

Obrigado.

Se você puder passar uma referência const, é melhor passar uma referência const. Ponteiro você só usa se quiser passar o valor null, ou se o objeto que recebe o ponteiro for se responsabilizar por excluí-lo.
A inicialização vai fazer uma atribuição à referência, não uma cópia do objeto.

Quanto ao outro erro, como está o arquivo .cpp? Aparentemente ele tentou fazer uma cópia do seu fstream.

Não tem nada no cpp:

[code]#include “State.h”
#include

State::State(string path) {
// Abrir o arquivo
this->readData();
}

State::~State() {
//dtor
}

void State::readData() {

}
[/code]

Então, não tá faltando chamar o construtor daquele fstream não?
Você não pode criar um fstream sem file nenhum associado.

Putz, preciso me acostumar.
Esse seria o caso de usar ponteiro? O caminho do arquivo é aquela string, não dá (acho) pra usar a lista de inicialização.

Agradeço sua paciência :slight_smile:

Por que vc tem um fstream de atributo? Isso é equivalente a ter um InputStream no java, não um File.

Geralmente você só cria o fstream na hora de ler pra valer o arquivo.
Até porque ele vai abrir o seu arquivo.

Ainda assim, daria para usar na lista de inicialização:

State::State(string path) : file(path, ios::out | ios::in | ios::binary) { // Abrir o arquivo this->readData(); }

PS: Recomendo FORTEMENTE que você leia o Effective C++. Ele explica em detalhes esses conceitos (lista de inicialização, construtores de cópia, uso de const& no lugar de ponteiros, etc).

Eu preciso ler o arquivo ao criar o State e depois gravar nesse arquivo.
Ao procurar sobre o assunto, achei e segui esse tutorial: http://www.cplusplus.com/doc/tutorial/files/
Mas não tem um equivalente ao File.

Você já me recomendou o livo, sei que preciso ler. Até hoje não consegui ler o de Java e sei que me faz bastante falta também.

Sim, mas ainda não entendi pq o ifstream tem que ser um atributo da sua classe. Não era melhor simplesmente cria-lo, ler o arquivo, e depois descarta-lo?

Mas depois eu vou precisar gravar nele de novo. A outra opção é guardar o caminho… faz diferença?

Eu tento achar as respostas, mas sempre acabo voltando aqui :slight_smile:

Meu problema é:
1- Quero ler alguns bytes de um arquivo.
2- Os bytes são lidos como string.
3- O tipo string é signed.

Resultado: se o valor do byte for acima de 127, tenho problemas.

Tem como usar algo tipo unsigned string ou terei que voltar aos ponteiros?

Edit: pensando melhor, tem ainda a opção de usar vector.
Edit2: não dá certo, pois o método read recebe char *, não unsigned char *… =/

Como assim o tipo string é signed? :shock:

Cada posição da string é char, não unsigned char. Pelo menos foi o que pude concluir.

string s = lerArquivo(); // fstream.read() int i = s[0];

Se aquele primeiro byte teve valor 0xFF, por exemplo, i vale -1 ao invés de 255. Notei que isso só acontece para valores acima de 127, que é o limite de um char com sinal.

Daí vi que existe um cast chamado reinterpret_cast, mas ainda não deu tempo de testar.

Ou não tem nada a ver?

Teste esse truque:

string s = lerArquivo(); // fstream.read() int i = s[0] & 0xFF;

Não deu pra testar no código real, mas nesse exemplo funcionou. Com avisos:

char c = 'º'; int i = c & 0xFF; cout << i << endl; // 186

[quote]novo.cpp:25:13: aviso: constante de caracter multi-caracter
novo.cpp: In function ‘int main()’:
novo.cpp:25: aviso: overflow in implicit constant conversion[/quote]

Qual seria a melhor opção pra mim? Usar um basic_string e copiar cada caractere (aplicando o and) da string lida do arquivo?

A notação com unsigned_char não é muito usual, pois até uns anos atrás o padrão só exigia que a classe std::string considerasse os tipos char e wchar_t (você pode ler mais sobre isso aqui).

Como o and é uma operação super barata, talvez seja uma boa você utiliza-lo.