I'm currently working on a class to create and read out packets send through the network, so far I have it working with 16bit and 8bit integers (Well unsigned but still).
Now the problem is I've tried numerous ways of copying it over but somehow the _buffer got mangled, it segfaulted, or the result was wrong.
I'd appreciate if someone could show me a working example.
My current code can be seen below.
Thanks, Xeross
Main
#include <iostream>
#include <stdio.h>
#include "Packet.h"
using namespace std;
int main(int argc, char** argv)
{
cout << "#################################" << endl;
cout << "# Internal Use Only #" << endl;
cout << "# Codename PACKETSTORM #" << endl;
cout << "#################################" << endl;
cout << endl;
Packet packet = Packet();
packet.SetOpcode(0x1f4d);
cout << "Current opcode is: " << packet.GetOpcode() << endl << endl;
packet.add(uint8_t(5))
.add(uint16_t(4000))
.add(uint8_t(5));
for(uint8_t i=0; i<10;i++)
printf("Byte %u = %x\n", i, packet._buffer[i]);
printf("\nReading them out: \n1 = %u\n2 = %u\n3 = %u\n4 = %s",
packet.readUint8(),
packet.readUint16(),
packet.readUint8());
return 0;
}
Packet.h
#ifndef _PACKET_H_
#define _PACKET_H_
#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
using namespace std;
class Packet
{
public:
Packet() : m_opcode(0), _buffer(0), _wpos(0), _rpos(0) {}
Packet(uint16_t opcode) : m_opcode(opcode), _buffer(0), _wpos(0), _rpos(0) {}
uint16_t GetOpcode() { return m_opcode; }
void SetOpcode(uint16_t opcode) { m_opcode = opcode; }
Packet& add(uint8_t value)
{
if(_buffer.size() < _wpos + 1)
_buffer.resize(_wpos + 1);
memcpy(&_buffer[_wpos], &value, 1);
_wpos += 1;
return *this;
}
Packet& add(uint16_t value)
{
if(_buffer.size() < _wpos + 2)
_buffer.resize(_wpos + 2);
memcpy(&_buffer[_wpos], &value, 2);
_wpos += 2;
return *this;
}
uint8_t readUint8()
{
uint8_t result = _buffer[_rpos];
_rpos += sizeof(uint8_t);
return result;
}
uint16_t readUint16()
{
uint16_t result;
memcpy(&result, &_buffer[_rpos], sizeof(uint16_t));
_rpos += sizeof(uint16_t);
return result;
}
uint16_t m_opcode;
std::vector<uint8_t> _buffer;
protected:
size_t _wpos; // Write position
size_t _rpos; // Read position
};
#endif // _PACKET_H_
Since you're using an std::vector for your buffer, you may as well let it keep track of the write position itself and avoid having to keep manually resizing it. You can also avoid writing multiple overloads of the add function by using a function template:
template <class T>
Packet& add(T value) {
std::copy((uint8_t*) &value, ((uint8_t*) &value) + sizeof(T), std::back_inserter(_buffer));
return *this;
}
Now you can write any POD type to your buffer.
implicitly:
int i = 5;
o.write(i);
or explictly:
o.write<int>(5);
To read from the buffer, you will need to keep track of a read position:
template <class T>
T read() {
T result;
uint8_t *p = &_buffer[_rpos];
std::copy(p, p + sizeof(T), (uint8_t*) &result);
_rpos += sizeof(T);
return result;
}
You will need to explicitly pass a type parameter to read. i.e.
int i = o.read<int>();
Caveat: I have used this pattern often, but since I am typing this off the top of my head, there may be a few errors in the code.
Edit: I just noticed that you want to be able to add strings or other non-POD types to your buffer. You can do that via template specialization:
template <>
Packet& add(std::string s) {
add(string.length());
for (size_t i = 0; i < string.length(); ++i)
add(string[i]);
return *this;
}
This tells the compiler: if add is called with a string type, use this function instead of the generic add() function.
and to read a string:
template <>
std::string read<>() {
size_t len = read<size_t>();
std::string s;
while (len--)
s += read<char>();
return s;
}
You could use std::string as internal buffer and use append() when adding new elements.
Thus adding strings or const char* would be trivial.
Adding/writing uint8 can be done with casting it to char, writing uint16 - to char* with length sizeof(uint16_t).
void write_uint16( uint16_t val )
{
m_strBuffer.append( (char*)(&var), sizeof(val) );
}
Reading uint16:
uint16_t read_int16()
{
return ( *(uint16_t*)(m_strBuffer.c_str() + m_nOffset) );
}
You appear to be attempting to print ten bytes out of the buffer when you've only added four, and thus you're running off the end of the vector. This could be causing your seg fault.
Also your printf is trying to print a character as an unsigned int with %x. You need to use static_cast<unsigned>(packet._buffer[i]) as the parameter.
Stylistically:
Packet packet = Packet(); could potentially result in two objects being constructed. Just use Packet packet;
Generally try to avoid protected attributes (protected methods are fine) as they reduce encapsulation of your class.
Related
I'm trying to figure out the best approach for transferring some data over the network. Here is what i'm hoping to achieve:
The application runs and computes some data:
int w = 5;
float x = 4.736;
std::string y = "Some String.";
std::vector<int> z;
z.push_back(1);
z.push_back(2);
z.push_back(3);
Then we put it in a binary container:
BinaryContainer Data;
Data.Write(w);
Data.Write(x);
Data.Write(y);
Data.Write(z);
We then transfer it over the network:
SendData(Data.c_str());
And read it out on the other side:
BinaryContainer ReceivedData(IncomingData);
int w = ReceivedData.Read();
float x = ReceivedData.Read();
std::string y = ReceivedData.Read();
std::vector<int> z = ReceivedData.Read();
The example above outlines how the basic functionality from a high level perspective should work. I've looked at many different serialization libraries and none seem to fit quite right. I'm leaning towards learning how to write the functionality myself.
Endianness doesn't matter. The architecture that reads and writes data will never differ.
We only need to store binary data inside the container. The reading application and writing application is exclusively responsible for reading data in the same order it was written. Only basic types need to be written, no entire arbitrary classes or pointers to things. Most importantly overall the speed in which this occurs should be of the highest priority because once the data is formulated, we need to write it to the container, transfer it over the network, and read it on the other end as fast as possible.
Network transmission is currently being done using the low level WinSock RIO API and we're moving data from the application to the wire as fast as possible already. Transmission latency across the wire will always be a much higher and variable rate. The point at which we serialize our data before transmission is the next step in the chain to ensure we are wasting as little time as possible before getting our data out on the wire.
New packets will be received very quickly, and as such the ability to preallocate resources would be beneficial. For example:
Serializer DataHandler;
...
void NewIncomingPacket(const char* Data)
{
DataHandler.Reset();
DataHandler.Load(Data);
int x = DataHandler.Read();
float y = DataHandler.Read();
...
}
I'm looking for input from community experts on which direction to go here.
If you don't care about endianness and only want to serialize trivial types than a simple memcpy will be the fastest and also safe. Just memcpy into/out of the buffer when serializing/deserializing.
#include <iostream>
#include <vector>
#include <cstring>
#include <cstdint>
#include <type_traits>
#include <cstddef>
template <std::size_t CapacityV>
struct BinaryContainer
{
BinaryContainer() :
m_write(0),
m_read(0)
{
}
template <typename T>
void write(const std::vector<T>& vec)
{
static_assert(std::is_trivial_v<T>);
// TODO: check if access is valid
const std::size_t bytes = vec.size() * sizeof(T);
std::memcpy(m_buffer + m_write, vec.data(), bytes);
m_write += bytes;
}
template <typename T>
void write(T value)
{
static_assert(std::is_trivial_v<T>);
// TODO: check if access is valid
const std::size_t bytes = sizeof(T);
std::memcpy(m_buffer + m_write, &value, bytes);
m_write += bytes;
}
template <typename T>
std::vector<T> read(std::size_t count)
{
static_assert(std::is_trivial_v<T>);
// TODO: check if access is valid
std::vector<T> result;
result.resize(count);
const std::size_t bytes = count * sizeof(T);
std::memcpy(result.data(), m_buffer + m_read, bytes);
m_read += bytes;
return result;
}
template <typename T>
T read()
{
static_assert(std::is_trivial_v<T>);
// TODO: check if access is valid
T result;
const std::size_t bytes = sizeof(T);
std::memcpy(&result, m_buffer + m_read, bytes);
m_read += bytes;
return result;
}
const char* data() const
{
return m_buffer;
}
std::size_t size() const
{
return m_write;
}
private:
std::size_t m_write;
std::size_t m_read;
char m_buffer[CapacityV]; // or a dynamically sized equivalent
};
int main()
{
BinaryContainer<1024> cont;
{
std::vector<std::uint32_t> values = {1, 2, 3, 4, 5};
// probably want to make serializing size part of the vector serializer
cont.write(values.size());
cont.write(values);
}
{
auto size = cont.read<std::vector<std::uint32_t>::size_type>();
auto values = cont.read<std::uint32_t>(size);
for (auto val : values) std::cout << val << ' ';
}
}
Demo: http://coliru.stacked-crooked.com/a/4d176a41666dbad1
I've written seriously, an header-only fast C++ library that should do what you want :-)
It provides both a serializer and a de-serializer.
Serialized data is portable across different architectures and endianness. No external dependencies.
seriously::Packer<1024> packer; // a 1024 byte serialization buffer
int32_t value1 = 83656;
bool value2 = true;
int16_t value3 = -2345;
std::string value4("only an example");
double value5 = -6.736;
std::vector<int64_t> value6;
value6.push_back(42);
value6.push_back(11);
value6.push_back(93);
packer << value1 << value2 << value3 << value4 << value5 << value6;
std::cout << "packed size: " << packer.size() << std::endl;
// packer.data() contains the serialized data
int32_t restored1;
bool restored2;
int16_t restored3;
std::string restored4;
double restored5 = -6.736;
std::vector<int64_t> restored6;
packer >> restored1 >> restored2 >> restored3 >> restored4 >> restored5 >> restored6;
std::cout << "unpacked: " << restored1 << " " << (restored2 ? "t" : "f") << " " << restored3 << " " << restored4 << " " << restored5 << std::endl;
std::vector<int64_t>::const_iterator it;
for (it = restored6.begin(); it != restored6.end(); it++) {
std::cout << *it << std::endl;
}
I want to implement RSA with padding but first I have to find out the length in bytes of the message which is a mpz_class item. Which function would be useful in cpp to accomplish this?
const mpz_class m(argv[1])
What is the length of m in bytes?
Thank you!
#Shawn's comment is correct: The bytes occupied in memory by your class are not what you should be concerned about. Not only does the location of the bytes in memory depend on how your compiler decides to pack them, but their order can also depend on the hardware used.
So, instead of doing some awkward and very fragile memcopy'ish thing that are almost guaranteed to break at some point, you should construct the message yourself (google keyword: Serialization). This also has the advantage that your class can contain stuff that you don't want to add to the message (caches with temp results, or other implementation/optimization stuff).
To the best of my knowledge C++ (unlike f.ex. C#) does not come with build in serialization support, but there are likely to exist libraries that can do a lot of it for you. Otherwise you just have to write your "data member to byte array" functions yourself.
Super simple example:
#include <vector>
#include<iostream>
class myClass
{
int32_t a;
public:
myClass(int32_t val) : a(val) {}
// Deserializer
bool initFromBytes(std::vector<uint8_t> msg)
{
if (msg.size() < 4)
return false;
a = 0;
for (int i = 0; i < 4; ++i)
{
a += msg[i] << (i * 8);
}
return true;
}
// Serializer
std::vector<uint8_t> toBytes()
{
std::vector<uint8_t> res;
for (int i = 0; i < 4; ++i)
{
res.push_back(a >> (i*8));
}
return res;
}
void print() { std::cout << "myClass: " << a << std::endl; }
};
int main()
{
myClass myC(123456789);
myC.print();
std::vector<uint8_t> message = myC.toBytes();
myClass recreate(0);
if (recreate.initFromBytes(message))
recreate.print();
else
std::cout << "Error" << std::endl;
return 0;
}
i'm working on a project in c++, and I have a vector of objects, where I want to push_back an object on the existing vector. However, when checking the size before and after the object is added, the size goes from 0 to 12297829382473034412 which puzzles me greatly. The code in question is the addCommodity function below. (I have created a smaller example of the same problem further down, so skip to "SMALL PROBLEM")
void Instance::addCommodity(std::vector<std::string> & tokens) {
/*if(tokens.size()!=5){
std::cerr << "Error in commodity data format"<< std::endl;
exit(-1);
}*/
// size_t so = std::atoi(tokens[1].c_str());
// size_t si = std::atoi(tokens[2].c_str());
// size_t demand = std::atoi(tokens[3].c_str());
// size_t ti = std::atoi(tokens[4].c_str());
std::cout << "size: " << this->_commodities->size() << "\n";
this->_commodities->push_back(Commodity(1,2,3,4)); // ???
std::cout << "size: " << this->_commodities->size() << "\n";
}
Here I have commented out the parts of the code which are used to read data from a string which was loaded from a file. Commodity is defined as follows:
#include "commodity.h"
Commodity::Commodity(size_t so, size_t si, size_t d, size_t ti):
_source(so),
_sink(si),
_demand(d),
_maxTime(ti)
{}
Commodity::~Commodity(){}
size_t Commodity::getSource() const{
return _source;
}
size_t Commodity::getSink() const {
return _sink;
}
size_t Commodity::getDemand() const {
return _demand;
}
size_t Commodity::getTime() const {
return _maxTime;
}
Where Instance is initialised as:
Instance::Instance(std::shared_ptr<Param> p, size_t n):
_params(p),
_nNodes(n)
{
this->_commodities.reset(new std::vector<Commodity>());
this->_arcs.reset(new std::vector<Arc>());
}
As mentioned before my issue lies in the addCommodity code, when trying to push_back a Commodity. Hopefully this is enough code to identify any stupid mistakes that I have made. I left out most of the other code for this project as it doesn't seem to have an impact on the addCommodity function.
The output received when calling the function is:
size: 0
size: 12297829382473034412
SMALL PROBLEM
Instead of showing all the code, I have run the push_back on the vector in main:
#include <iostream>
#include <memory>
#include <sys/time.h>
#include <vector>
#include "commodity.h"
int main(int argc, char* argv[]){
std::shared_ptr< std::vector<Commodity>> commodities;
commodities.reset(new std::vector<Commodity>());
std::cout << "size: " << commodities->size() << "\n";
size_t a = 1;
size_t b = 2;
size_t c = 3;
size_t d = 4;
commodities->emplace_back(Commodity(a,b,c,d));
std::cout << "size: " << commodities->size() << std::endl;
return 0;
}
This is basically a smaller instance of the same code. The commodity cpp and h files are as follows:
#include "commodity.h"
Commodity::Commodity(size_t so, size_t si, size_t d, size_t ti):
_source(so),
_sink(si),
_demand(d),
_maxTime(ti)
{}
Commodity::~Commodity(){}
size_t Commodity::getSource() const{
return _source;
}
size_t Commodity::getSink() const {
return _sink;
}
size_t Commodity::getDemand() const {
return _demand;
}
size_t Commodity::getTime() const {
return _maxTime;
}
The header file:
#ifndef CG_MCF_COMMODITY_H
#define CG_MCF_COMMODITY_H
#include <stdlib.h>
class Commodity {
public:
Commodity(size_t so, size_t si, size_t d, size_t t);
~Commodity();
size_t getSource() const;
size_t getSink() const;
size_t getDemand() const;
size_t getTime() const;
private:
size_t _source;
size_t _sink;
size_t _demand;
size_t _maxTime;
};
#endif /*CG_MCF_COMMODITY_H*/
The output received when calling the function is:
size: 0
size: 12297829382473034412
Your Commodity class violates the rule of 0/3/5.
Your code (inexplicably) does this:
commodities->emplace_back(Commodity(a,b,c,d));
This is really strange. Presumably, you're calling emplace_back to avoid having to construct a separate Commodity from the one in the vector. But you force that to happen by explicitly constructing a separate Commodity as the parameter to emplace_back.
That invokes Commodity's copy constructor to construct the Commodity in the vector as a copy of the one you explicitly created. Except Commodity doesn't have one. Most likely, the real Commmodity class needs one, since it has a destructor.
I have come up with the following structure to declare various formats if messages that are to be received from the network:
#include <stdint.h>
#include <iostream>
#include <string.h>
template<int T>
struct uint
{
static uint<T> create(uint64_t value)
{
uint<T> r = {value};
return r;
}
uint(uint64_t value)
{
v = value;
}
uint()
{}
uint<T>& operator =(uint64_t value)
{
v = value;
return *this;
}
operator uint64_t() const
{
return (uint64_t)v;
}
unsigned long long v:T;
}__attribute__((packed));
example:
typedef uint<5> second_t;
suppose one of the message formats (which are auto-generated via some process) is like this:
struct seconds
{
char _type;
second_t _second;
} __attribute__((packed));
Now suppose I would like to populate an instance of the above messahe using a string:
int main()
{
seconds ii;
const char *i = "123456";
// memset, memcpy,sprintf... ??? what to use here?
std::cout << ii._type << " " << ii._second << std::endl;
}
Given a stream 123456, I expect the instance of the seconds (ii) structure to have char ii._type = '1' and integer ii._second = 23456. But I dont know how to do that. Do you have a clue how i can do that? and do you have any suggestion how to improve the basic structure?
thanks
You have a number of easier and more reliable options available that require almost no work.
check out google protocol buffers (platform independent message serialisation and deserialisation): https://developers.google.com/protocol-buffers/
or boost::serialization - (probably faster, but not platform-independant) http://www.boost.org/doc/libs/1_58_0/libs/serialization/doc/index.html
I made my own database format, and it sadly required too much memory and the size of it got horrendous and upkeep was horrible.
So I'm looking for a way to store an array of a struct that's in an object into a table.
I'm guessing I need to use a blob, but all other options are welcome. An easy way to implement a blob would be helpful as well.
I've attached my saving code and related structures(Updated from my horrible post earlier)
#include "stdafx.h"
#include <string>
#include <stdio.h>
#include <vector>
#include "sqlite3.h"
using namespace std;
struct PriceEntry{
float cardPrice;
string PriceDate;
int Edition;
int Rarity;
};
struct cardEntry{
string cardName;
long pesize;
long gsize;
vector<PriceEntry> cardPrices;
float vThreshold;
int fav;
};
vector<cardEntry> Cards;
void FillCards(){
int i=0;
int j=0;
char z[32]={0};
for(j=0;j<3;j++){
cardEntry tmpStruct;
sprintf(z, "Card Name: %d" , i);
tmpStruct.cardName=z;
tmpStruct.vThreshold=1.00;
tmpStruct.gsize=0;
tmpStruct.fav=1;
for(i=0;i<3;i++){
PriceEntry ss;
ss.cardPrice=i+1;
ss.Edition=i;
ss.Rarity=i-1;
sprintf(z,"This is struct %d", i);
ss.PriceDate=z;
tmpStruct.cardPrices.push_back(ss);
}
tmpStruct.pesize=tmpStruct.cardPrices.size();
Cards.push_back(tmpStruct);
}
}
int SaveCards(){
// Create an int variable for storing the return code for each call
int retval;
int CardCounter=0;
int PriceEntries=0;
char tmpQuery[256]={0};
int q_cnt = 5,q_size = 256;
sqlite3_stmt *stmt;
sqlite3 *handle;
retval = sqlite3_open("sampledb.sqlite3",&handle);
if(retval)
{
printf("Database connection failed\n");
return -1;
}
printf("Connection successful\n");
//char create_table[100] = "CREATE TABLE IF NOT EXISTS users (uname TEXT PRIMARY KEY,pass TEXT NOT NULL,activated INTEGER)";
char create_table[] = "CREATE TABLE IF NOT EXISTS Cards (CardName TEXT, PriceNum NUMERIC, Threshold NUMERIC, Fav NUMERIC);";
retval = sqlite3_exec(handle,create_table,0,0,0);
printf( "could not prepare statemnt: %s\n", sqlite3_errmsg(handle) );
for(CardCounter=0;CardCounter<Cards.size();CardCounter++){
char Query[512]={0};
for(PriceEntries=0;PriceEntries<Cards[CardCounter].cardPrices.size();PriceEntries++){
//Here is where I need to find out the process of storing the vector of PriceEntry for Cards then I can modify this loop to process the data
}
sprintf(Query,"INSERT INTO Cards VALUES('%s', %d, %f, %d)",
Cards[CardCounter].cardName.c_str(),
Cards[CardCounter].pesize,
Cards[CardCounter].vThreshold,
Cards[CardCounter].fav); //My insert command
retval = sqlite3_exec(handle,Query,0,0,0);
if(retval){
printf( "Could not prepare statement: %s\n", sqlite3_errmsg(handle) );
}
}
// Insert first row and second row
sqlite3_close(handle);
return 0;
}
I tried googling but my results didn't suffice.
You have two types here: Cards and PriceEntries. And for each Card there can be many PriceEntries.
You can store Cards in one table, one Card per row. But you're puzzled about how to store the PriceEntries, right?
What you'd normally do here is have a second table for PriceEntries, keyed off a unique column (or columns) of the Cards table. I guess the CardName is unique to each card? Let's go with that. So your PriceEntry table would have a column CardName, followed by columns of PriceEntry information. You'll have a row for each PriceEntry, even if there are duplicates in the CardName column.
The PriceEntry table might look like:
CardName | Some PE value | Some other PE value
Ace | 1 | 1
Ace | 1 | 5
2 | 2 | 3
and so on. So when you want to find the array of PriceEntries for a card, you'd do
select * from PriceEntry where CardName = 'Ace'
And from the example data above you'd get back 2 rows, which you could shove into an array (if you wanted to).
No need for BLOBs!
This is a simple serialization and deserialization system. The class PriceEntry has been extended with serialization support (very simply). Now all you have to do is serialize a PriceEntry (or a set of them) to binary data and store it in a blob column. Later on, you get the blob data and from that deserialize a new PriceEntry with the same values. An example of how it is used is given at the bottom. Enjoy.
#include <iostream>
#include <vector>
#include <string>
#include <cstring> // for memcpy
using std::vector;
using std::string;
// deserialization archive
struct iarchive
{
explicit iarchive(vector<unsigned char> data)
: _data(data)
, _cursor(0)
{}
void read(float& v) { read_var(v); }
void read(int& v) { read_var(v); }
void read(size_t& v) { read_var(v); }
void read(string& v) { read_string(v); }
vector<unsigned char> data() { return _data; }
private:
template <typename T>
void read_var(T& v)
{
// todo: check that the cursor will not be past-the-end after the operation
// read the binary data
std::memcpy(reinterpret_cast<void*>(&v), reinterpret_cast<const void*>(&_data[_cursor]), sizeof(T));
// advance the cursor
_cursor += sizeof(T);
}
inline
void
read_string(string& v)
{
// get the array size
size_t sz;
read_var(sz);
// get alignment padding
size_t padding = sz % 4;
if (padding == 1) padding = 3;
else if (padding == 3) padding = 1;
// todo: check that the cursor will not be past-the-end after the operation
// resize the string
v.resize(sz);
// read the binary data
std::memcpy(reinterpret_cast<void*>(&v[0]), reinterpret_cast<const void*>(&_data[_cursor]), sz);
// advance the cursor
_cursor += sz + padding;
}
vector<unsigned char> _data; // archive data
size_t _cursor; // current position in the data
};
// serialization archive
struct oarchive
{
void write(float v) { write_var(v); }
void write(int v) { write_var(v); }
void write(size_t v) { write_var(v); }
void write(const string& v) { write_string(v); }
vector<unsigned char> data() { return _data; }
private:
template <typename T>
void write_var(const T& v)
{
// record the current data size
size_t s(_data.size());
// enlarge the data
_data.resize(s + sizeof(T));
// store the binary data
std::memcpy(reinterpret_cast<void*>(&_data[s]), reinterpret_cast<const void*>(&v), sizeof(T));
}
void write_string(const string& v)
{
// write the string size
write(v.size());
// get alignment padding
size_t padding = v.size() % 4;
if (padding == 1) padding = 3;
else if (padding == 3) padding = 1;
// record the data size
size_t s(_data.size());
// enlarge the data
_data.resize(s + v.size() + padding);
// store the binary data
std::memcpy(reinterpret_cast<void*>(&_data[s]), reinterpret_cast<const void*>(&v[0]), v.size());
}
vector<unsigned char> _data; /// archive data
};
struct PriceEntry
{
PriceEntry()
{}
PriceEntry(iarchive& in) // <<< deserialization support
{
in.read(cardPrice);
in.read(PriceDate);
in.read(Edition);
in.read(Rarity);
}
void save(oarchive& out) const // <<< serialization support
{
out.write(cardPrice);
out.write(PriceDate);
out.write(Edition);
out.write(Rarity);
}
float cardPrice;
string PriceDate;
int Edition;
int Rarity;
};
int main()
{
// create a PriceEntry
PriceEntry x;
x.cardPrice = 1;
x.PriceDate = "hi";
x.Edition = 3;
x.Rarity = 0;
// serialize it
oarchive out;
x.save(out);
// create a deserializer archive, from serialized data
iarchive in(out.data());
// deserialize a PriceEntry
PriceEntry y(in);
std::cout << y.cardPrice << std::endl;
std::cout << y.PriceDate << std::endl;
std::cout << y.Edition << std::endl;
std::cout << y.Rarity << std::endl;
}