konto usunięte

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Witam.
Od jakiegoś czasu gryzie mnie pewien problem. Jak zapisać i odczytać z pliku klasę podaną w ten sposób:

class klasa
{
public:
int w;
string g;
klasa(int ww,string gg): w(ww),g(gg){}
};

i obiekty utworzone:

klasa x(45,"Taki sobie tekst.");
klasa y(0,"-");

Zapis i odczyt realizuję w ten sposób (kod z książki, jednak w tamtym przypadku klasa składała się jedynie z dwóch liczb int):

ofstream plik;
plik.open("nazwa.txt",ios::trunc|ios::binary);
plik.write((char*)&x,sizeof(x));
plik.close();
ifstream plikb;
plikb.open("nazwa.txt",ios::binary);
plikb.read((char*)&y,sizeof(x));
plikb.close();

Po wywołanie kody wyskakuje błąd odczytu/zapisu. Udało mi się dojść, że to problem tego, że przy wykonywaniu linii z poleceniem read występuje naruszenie ponieważ nie można w ten sposób "dobrać" się do stringa w klasie. Problem udało mi się obejść zastępując string tablicą char. Wolałbym jednak mieć możliwość stworzenie dowolnie długiego tekstu w klasie, stąd moje pytanie czy można rozwiązać powyższy problem w jakiś prosty sposób?
Daniel Łysiak

Daniel Łysiak Specjalista w
sprawach
audiowizualnych

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Albo nie można się dobrać, albo przekroczony został zarezerwowany obszar pamięci w klasie y. Jeśli czytanie przez bufor char[MAX_SIZE] jest niewygodne, sugeruję nie używać tej metody a korzystać z innych dobrodziejstw stl-a. Swoją drogą takie niskopoziomowe dobieranie się do int-a może być niebezpieczne, ponieważ int w 32 bitowym środowisku ma 32 bity a w 64 bitowym 64.

Może ten tekst coś pomoże:

http://www.decompile.com/cpp/faq/file_to_vector.htm
Szymon Kubisiak

Szymon Kubisiak Developer aplikacji
mobilnych Android

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Sposób zapisu &obiekt, sizeof(obiekt) działa TYLKO przy obiektach nie zawierających żadnych pointerów. Ani zawierających obiektów zawierających pointery, itd...
Generalnie nie możesz go stosować jeżeli twój obiekt zawiera inne klasy, których bajtowego layoutu nie znasz. (ja np. nie wiem jak w pamięci leży string, więc w przykładzie użyję czegoś prostszego)

Pomyśl o takim obiekcie:
class CX
{
int iA;
char* lpszB;
int iC;
int iD;
int iE;
}

Jeżeli teraz go zapiszesz w ten sposób, co znajdzie się w lpszB? Adres pamięci gdzie leży twój string, a nie sam string. Teraz po wczycie masz adres który był ważny przy poprzednim uruchomieniu, ale teraz pamięć na którą wskazuje już nie jest twoja.

To co trzeba zrobić, to rozłożyć zapis na części:
write(&x, sizeof(x) );
write(x.lpszB, sizeof(strlen(lpszB)) ;

Koniecznie obejrzyj ten plik w hexedytorze, to od razu zorientujesz się czy czegoś brakuje.

No dobrze, klasa jest zapisana CAŁA, (nawet więcej niż cała, bo zapisaliśmy wartość pointera, która jest zbędna)
Ale jak teraz ją wczytać? Długość tekstu jest znana przy zapisie, ale przy odczycie *trochę* się nie opłaca jechać bajt po bajcie i czekać na '\0'.
Problem zapisu "rozgałęzionych w pamięci" danych jest rozwiązany, ale teraz jest kolejny, problem wczytu-zapisu danych o nieokreślonej długości:

write(&x, sizeof(x) );
int len = sizeof(strlen(lpszB) +1;//jeden bajt więcej na '\0'
write(len, sizeof(len));
write(x.lpszB, len);

Teraz możesz wczytać:

read(&x, sizeof(x));
int len =0;
read(&len, sizeof(len));
x.lpszB = new char(len); //śmieć wczytany do lpszB zostaje zastąpiony ważnym adresem
read(x.lpszB, len);

Ponieważ piszesz w C++, więc najlepiej takie zamieszanie włożyć do publicznej metody Write(*ofstream pFileToWriteTo) a membery zamknąć privatem.

Klasa jak "string" sama powinna mieć już coś takiego. Wtedy każda klasa zapisuje swoje membery proste, jak inty a następnie woła Write memberom-klasom. W ten sposób jednym Write'm pięknie zapisze i wczyta się całe drzewko.

Oczywiście, można użyć gotowej biblioteki i wszystko zadziała bez zrozumienia problemu : )
Jak pisze Daniel, należy używać typów (intów) których wielkość jest zablokowana i niezależna od kompilatora. Czyli nie int, a jakieś predefinowane typu INT32.
Przydaje się również #pragma pack(1), coby obiekty miały naprawdę wielkość taką jak powinne, bez zaokrąglania w górę.Szymon Kubisiak edytował(a) ten post dnia 19.06.11 o godzinie 11:01

konto usunięte

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Daniel: Chyba pomerdało ci się z rozmiarem wskaźnika ;)

int może być jedynie 32 lub 16 bitowy, jeżeli użyjesz 16 bitowego kompilatora.
64bit może mieć typ long (GCC na 64bitowych platformach *nixowych) lub long long (wszystkie kompilatory, niezależnie od platformy).

http://www.cppreference.com/wiki/language/typesMaciej O. edytował(a) ten post dnia 19.06.11 o godzinie 10:22
Szymon Kubisiak

Szymon Kubisiak Developer aplikacji
mobilnych Android

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Daniel ma rację, jedyne co gwarantuje int to że jest co najmniej równie duży jak char. Na stronie którą sam przytoczyłeś:
int - has at least as much storage as short int. Should have the natural size suggested by the architecture.Szymon Kubisiak edytował(a) ten post dnia 19.06.11 o godzinie 10:27

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Poniżej prosty kod zapisując dane obiektu do pliku.


#include<iostream>
#include<fstream> // biblioteka do plików

using namespace std;

class klasa
{
int w;
string b;
public:
klasa(int ww, string bb): w(ww), b(bb) {} ; // konstruktor
friend ostream & operator<< (ostream &wyjscie, const klasa &s);
};

ostream & operator<< (ostream &wyjscie, const klasa &s)
{
wyjscie << "W : " <<s.w <<" "<< "G : " <<s.b<<endl;
return wyjscie;
}


int main()
{
klasa x(45,"Taki sobie tekst.");
klasa y(0,"-");

ofstream plik;
plik.open("test.txt");
plik<<x;
plik.close();

return 0;
}



Jeśli mowa była o uzyskaniu takiego wyniku, to są sądzę, że i odczytać też można przez przeciążenie operatora '>>'.

konto usunięte

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Ok, my bad. Założyłem że kompilujemy "popularnym" kompilatorem na "popularną" platformę ;)
Szymon Kubisiak

Szymon Kubisiak Developer aplikacji
mobilnych Android

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Dlatego nigdy nie należy ufać gołemu intowi : )
typedef __int32 INT32 i jazda!
http://msdn.microsoft.com/en-us/library/s3f49ktz%28v=V...

konto usunięte

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Osobiście używam int32_t i tym podobnych z cstdint/stdint.h
Szymon Kubisiak

Szymon Kubisiak Developer aplikacji
mobilnych Android

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Luksus C99. Ech, pomarzyć : )

konto usunięte

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Dzięki wielkie za odpowiedzi. Rozwiązanie Szymona to jest chyba to co mnie interesowało właśnie;). Tylko muszę je jeszcze przetestować. Czyli jest tak jak myślałem, że nie da się zrealizować zapisu/odczytu takiej klasy za pomocą jednego polecenie write/read. A przykład w książce był najwyraźniej tak dobrany, że się udało. Generalnie moim zamysłem było stworzenie "bazy danych", gdzie powiedzmy w vector będę wkładać właśnie powyższą klasę. Głównym założeniem jednak było to, że tekst wpisany przez użytkownika do obiektu może być dowolnie długi i nie znamy jego długości. A program po ponownym uruchomieniu wczyta sobie od razu całą bazę danych z pliku. Zapis chciałem koniecznie zrealizować binarnie za pomocą write, aby dane były nieczytelne z poziomu pliku.
Szymon Kubisiak

Szymon Kubisiak Developer aplikacji
mobilnych Android

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Nie da się jednym ciągiem w tablicy zapisać rzeczy o różnych długościach.
Co najwyżej możesz najpierw zapisać całą tablicę obiektów o stałej długości a za nią wszystkie zmienne "dodatki" (takie nieobiektowe podejście).
Jeżeli chcesz by plik nie był czytelny, przejedź bajty XORem.
string zapisany "binarnie" i tak jest czytelny, bo to znaki ascii.
L P

L P podskala.net

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Serializację binarną w C++ może ułatwić boost..
http://www.boost.org/doc/libs/1_37_0/libs/serializatio...

Jeżeli to ma być tylko utajnianie treści - Szymona podpowiedź, motoda z XOR'em jest b. OK. Dodałbym do tego zapisywanie/odczytywanie rekordów wspomnianej bazy do/z jakieś konkretnej struktury opisującej rekord (bardziej.. jak w C).

konto usunięte

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Biblioteka boost::serialization jest fajna, ale ma swoje wady które (przynajmniej dla mnie) są dyskwalifikujące:
- trzeba modyfikować praktycznie każdą klasę, która ma być serializowana. Istnieje teoretycznie metoda "non-intrusive" ale nie zawsze ją się da zastosować - problematyczne stają się pola prywatne klasy dla których nie ma bezpośrednich get/setterów
- skompilowana binarka puchnie niesamowicie
- pod win32 binarka "zyskuje" mnóstwo eksportów - twórcy boost::serialization chcieli w ten sposób poinstruować linker żeby nie usuwał części kodu, które wg niego są nieużywane a służą do deserializacji klas bazowych. Mnie akurat przeszkadza to że mój .exe a w szczególności .dll eksportuje coś o czym nie wiem i nie mam nad tym kontroli.

konto usunięte

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Maciej O.:
Biblioteka boost::serialization jest fajna, ale ma swoje wady które (przynajmniej dla mnie) są dyskwalifikujące:
- skompilowana binarka puchnie niesamowicie

Zgadzam się. To jest w ogóle największy problem boosta. Na szczęście sporo rzeczy jest w TR1, aczkolwiek serializacji z tego co wiem to tam nie ma niestety.

konto usunięte

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Korzystając z rady udało mi się jednak stworzyć program, w którym składową obiektu jest klasa. Jednak przy zapisie zamieniam na c-string, i zapisuje w kolejność liczba obiektu/dlugosc c-stringu/c-string. Byłbym wdzięczny gdybyście rzucili okiem czy nie ma żadnych ukrytych błędów, bo program wydaje się działać prawidłowo.

class klasa
{
public:
__int32 liczba;
string tekst;
klasa(int ww,string sk): liczba(ww) {
tekst=sk;
}
};

int _tmain(int argc, _TCHAR* argv[])
{
__int32 dlugosc;
klasa obx(35,"Taki sobie tekst.");
klasa oby(0,"Do skasowania");

ofstream plik2a;
plik2a.open("niefor_tekst.txt",ios::trunc);
plik2a.write((char*)&obx.liczba,sizeof(__int32));
dlugosc=obx.tekst.length()+1;
plik2a.write((char*)&dlugosc,sizeof(__int32));
plik2a.write(obx.tekst.c_str(),dlugosc);
plik2a.close();

ifstream plik2b;
plik2b.open("niefor_tekst.txt");
plik2b.read((char*)&oby.liczba,sizeof(__int32));
plik2b.read((char*)&dlugosc,sizeof(__int32));
char *wsk=new char[dlugosc];
plik2b.read(wsk,dlugosc);
oby.tekst=wsk;
delete [] wsk;
plik2b.close();

cout<<oby.liczba<<endl;
cout<<oby.tekst<<endl;
system("pause");
return 0;
}Michał Z. edytował(a) ten post dnia 19.06.11 o godzinie 19:41
L P

L P podskala.net

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Poniżej wklejam prosty kod w C. Prog. pisany na szybko, niekompletny interfejs, zapewne z bledami ale powinno się kompilować :). Chodzi mi o ten właśnie przepis. Można to zrobić prosto - jak kto lubi.

EDIT: obsługa <b>dowolnej</b> długości tekstu.


/**
* LP
* g++ simple_file_db.cpp -o sfdb
*/

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include <iostream>
#include <assert.h>

using namespace std;


// Database header
typedef struct s_db_head {
int records_cnt; // Others ...
} db_head_t;

// Database context
typedef struct s_db_ctx {
db_head_t head;
int file_size;
FILE *fp;
// Others ...
} db_ctx_t;

// Simple record definition
typedef struct s_db_record {
int tekst_len;
int liczba;
uint8_t tekst[];
} db_record_t;

static int
db_head_update(db_ctx_t *ctx)
{

rewind(ctx->fp);
assert(fwrite(&ctx->head, 1, sizeof(db_head_t), ctx->fp) > 0);
return (0);
}

static int
db_open(const char *filepath, db_ctx_t *ctx)
{
if (filepath == NULL || ctx == NULL)
return (1);

if ((ctx->fp = fopen(filepath, "r+")) == NULL)
if ((ctx->fp = fopen(filepath, "w")) == NULL)
return (2);

fseek(ctx->fp, 0, SEEK_END);
ctx->file_size = ftell(ctx->fp);
rewind(ctx->fp);

if (ctx->file_size == 0) {// New file ?
ctx->head.records_cnt = 0;
db_head_update(ctx);
ctx->file_size += sizeof(db_head_t);
} else
assert(fread(&ctx->head, sizeof(db_head_t), 1, ctx->fp) > 0);

return (0);
}

static void
db_close(db_ctx_t *ctx)
{
if (ctx == NULL)
return;

if (ctx->fp != NULL) {
fclose(ctx->fp);
ctx->fp == NULL;
}
}


static int
db_record_create(int liczba, const string tekst, db_record_t **recordp)
{
const int tekst_len = tekst.size() + 1;
unsigned char *ptr;
if ((ptr = (unsigned char *)malloc(sizeof(db_record_t) + tekst_len)) == NULL)
return (1);

(*recordp) = (db_record_t *)ptr;
(*recordp)->tekst_len = tekst_len;
(*recordp)->liczba = liczba;
memcpy((*recordp)->tekst, tekst.c_str(), tekst_len);
return (0);
}

static int
db_record_add(db_ctx_t *ctx, db_record_t *record)
{
fseek(ctx->fp, 0, SEEK_END);

assert(fwrite((char *)record, 1, sizeof(db_record_t) + record->tekst_len, ctx->fp) > 0);
ctx->head.records_cnt++;
db_head_update(ctx);

return (0);}


static int
db_record_read(db_ctx_t *ctx, db_record_t **recordp)
{
unsigned char *ptr;
db_record_t r;

assert(fread(&r, sizeof(db_record_t), 1, ctx->fp) > 0);

if ((ptr = (unsigned char *)malloc(sizeof(db_record_t) + r.tekst_len)) == NULL)
return (1);

(*recordp) = (db_record_t *)ptr;
(*recordp)->tekst_len = r.tekst_len + 1;
(*recordp)->liczba = r.liczba;
assert(fread((*recordp)->tekst, r.tekst_len, 1, ctx->fp) > 0);

return (0);
}

static void
db_record_done(db_record_t *record)
{
if (record == NULL)
return;

free(record);
record = NULL;
}

static int
db_record_first(db_ctx_t *ctx, db_record_t **recordp)
{
if (ctx->head.records_cnt <= 0)
return (1);

fseek(ctx->fp, sizeof(db_head_t), SEEK_SET);
db_record_read(ctx, recordp);

return (0);
}

static int
db_record_next(db_ctx_t *ctx, db_record_t **recordp)
{
if (ftell(ctx->fp) >= ctx->file_size)
return (1);
db_record_read(ctx, recordp);

return (0);
}

static int
db_insert(db_ctx_t *ctx, int liczba, const string tekst)
{
db_record_t *record = NULL;
// Create new record from data
db_record_create(liczba, tekst, &record);
// Add this record to database
db_record_add(ctx, record);
// Fini record
db_record_done(record);
return (0);
}

#define DB_FOREACH_RECORD(cnt, recordp, ctx) \
for ((cnt) = 0, db_record_first(ctx, recordp); cnt < (ctx)->head.records_cnt; db_record_next(ctx, recordp), (cnt)++)



/* Simple interface for C++ ? */

class SimpleFileDB {
private:
db_ctx_t ctx;
public:
SimpleFileDB() {}
SimpleFileDB(const string filepath) {
open(filepath);
}
~SimpleFileDB() {
db_close(&ctx);
}
int open(const string filepath) {
return db_open(filepath.c_str(), &ctx);
}
void close(void) {
db_close(&ctx);
}
int insert(int liczba, const string tekst) {
return db_insert(&ctx, liczba, tekst);
}
/* Others.. */
};


int
main(void)
{
int i;
db_ctx_t ctx;
db_record_t *record = NULL;
// Open database
db_open("test.db", &ctx);

// Insert records
db_insert(&ctx, 2, "text krotki");
db_insert(&ctx, 12, "text dluzszy..");

// Print some info
cout << "[ DB ] file_size = " << ctx.file_size << endl;
cout << "[ DB ] records_cnt = " << ctx.head.records_cnt << endl;


// Print data from each record
DB_FOREACH_RECORD(i, &record, &ctx) {
if (record) {
cout << "[ DB ] record(" << i << ") liczba: " << record->liczba << ", tekst: " << record->tekst << endl;
db_record_done(record);
record = NULL;
}
}
// Close database
db_close(&ctx);

/** C++ interface */
SimpleFileDB *db = new SimpleFileDB("test.db");
db->insert(12, "c++ db-object-insert!");
return (0);
}


Łukasz Podkalicki edytował(a) ten post dnia 20.06.11 o godzinie 01:07

konto usunięte

Temat: Zapis/odczyt z pliku obiektu ze string'iem

hmmm a nie prościej jest stworzyć dwie funkcje składowe klasy, które będą odpowiadały za zapisanie i odczytanie danych z pliku? Funkcja main wtedy o wiele ładniej wygląda...
Jakby składniki klasy były prywatne to byś już tak nie poszalał...
A w kodzie wydaje mi się że powinno być tak:

...
oby.tekst = *wsk;
...

konto usunięte

Temat: Zapis/odczyt z pliku obiektu ze string'iem

Proponuję zacząć od wyboru formatu pliku: CSV, JSON lub Google Protocol Buffers.

A potem znaleźć odpowiednią bibliotekę.

A może Boost.PropertyTree ?Piotr Likus edytował(a) ten post dnia 21.06.11 o godzinie 09:18

Następna dyskusja:

odczyt tresci i struktury p...




Wyślij zaproszenie do