I'm writing a packet processor and have the following classes:
class Parser {
private:
Packet* curr_pkt;
public:
void parsePacket(unsigned char byte) {
if(byte == SyncPacket::headerVal) {
SyncPacket pkt;
curr_pkt = &pkt;
}
if(byte == SyncPacket::headerVal) {
TypeAPacket pkt;
curr_pkt = &pkt;
}
}
};
class Packet {
public:
void parseByte(unsigned char byte) {}
};
class SyncPacket : public Packet {
public:
static const unsigned char headerVal = 0x0A;
void parseByte(unsigned char byte) {
<do sync>
}
};
class TypeAPacket : public Packet {
public:
static const unsigned char headerVal = 0x0B;
void parseByte(unsigned char byte) {
<do type A decode>
}
};
int main() {
std::vector<unsigned char> chars;
<stream in characters>
Parser pktParser;
for(unsigned char byte: bytes) {
pktParser.parseByte(byte);
}
}
This seems to work fine and is in fact what I think polymorphism is for. But my question is: should I be worried about the raw pointer use here? Would there be a more recommended way to do this via smart pointers?
For example, in this line:
if(byte == SyncPacket::headerVal) {
SyncPacket pkt;
curr_pkt = &pkt;
}
Technically, pkt is out of scope after the if statement, so normally it's lifetime would be over. But because curr_pkt is pointing to it, it lives on. This is a potential issue, isn't it?
Technically, pkt is out of scope after the if statement, so normally it's lifetime would be over.
This is spot on correct, but there's nothing technically about it.
But because curr_pkt is pointing to it, it lives on.
This is not correct though, the object does not exist anymore at that point, the pointer is deemed dangling, and any attempt to dereference it would be Undefined Behavior.
If you managed to make this work, then you simply got lucky.
Would there be a more recommended way to do this via smart pointers?
Not so much a "more recommended" way, but a way that actually works, yes.
You can indeed use smart pointers to accomplish what your code is trying to express. (You don't need smart pointers to get there, but it does make it a lot safer).
class Parser {
private:
std::unique_ptr<Packet> curr_pkt;
public:
void parsePacket(unsigned char byte) {
if(byte == SyncPacket::headerVal) {
curr_pkt = std::make_unique<SyncPacket>();
}
if(byte == SyncPacket::headerVal) {
curr_pkt = std::make_unique<TypeAPacket>();
}
}
};
Yes this returning the address of a local variable is not going to work.
And there is more to polymorphism, as explained in this example
#include <cstdint>
#include <memory>
#include <vector>
//-------------------------------------------------------------------------------------------------
// Packet is going to be an abstract base class
// (closest thing C++ has to an interface)
class Packet_itf
{
public:
// Dynamic polymorphism means virtual functions
// for interfaces this will be pure virtual functions
virtual void parseByte(unsigned char byte) = 0;
// destructor needs to be virtual
virtual ~Packet_itf() = default;
protected:
// protected constructor Packet_itf is not supposed to be instantiated
Packet_itf() = default;
// todo delete copy/move/assignment rule of 5
};
//-------------------------------------------------------------------------------------------------
// define a null strategy for the interface
// an implementation that does nothing
class NullPacket :
public Packet_itf
{
void parseByte(unsigned char byte) override
{
}
};
//-------------------------------------------------------------------------------------------------
// here are your packets
class SyncPacket :
public Packet_itf
{
public:
static const std::uint8_t headerVal = 0x0A;
void parseByte(unsigned char byte) override
{
//<do sync>
}
};
//-------------------------------------------------------------------------------------------------
class TypeAPacket :
public Packet_itf
{
public:
static const std::uint8_t headerVal = 0x0B;
void parseByte(unsigned char byte) override
{
//<do type A decode>
}
};
//-------------------------------------------------------------------------------------------------
// Dynamic polymorphism renamed parser to factory
// factory pattern
class PacketFactory
{
public:
static std::unique_ptr<Packet_itf> createPacket(const std::uint8_t byte)
{
switch (byte)
{
case SyncPacket::headerVal:
{
return std::make_unique<SyncPacket>();
}
break;
case TypeAPacket::headerVal:
{
return std::make_unique<TypeAPacket>();
}
break;
default:
// error handling here
break;
}
// return a default packet that does nothing
// Null strategy pattern
return std::make_unique<NullPacket>();
}
};
//-------------------------------------------------------------------------------------------------
int main()
{
//<stream in characters>
// note 0x02 isn't know yet so ut will result in a NullPacket
// no ifs in the loop need to handle that case.
std::vector<std::uint8_t> bytes{ SyncPacket::headerVal, TypeAPacket::headerVal, 0x02 };
for (const auto byte : bytes)
{
// now packet is a unique ptr that will live for the lifetime
// of this for loop then it will be deleted.
auto packet = PacketFactory::createPacket(SyncPacket::headerVal);
std::vector<std::uint8_t> data{ 0x00,0x00,0x01,0x03 };
for (const auto value : data)
{
packet->parseByte(value);
}
}
return 0;
}
Related
I'm debugging a program (segfault) using LLDB on MacOS Big Sur. I have these types:
class Pkt {
public:
std::uint8_t header;
Pkt(std::uint8_t header): header(header) {
}
};
class Pkt1 : public Pkt {
public:
static inline const std::uint8_t headerVal = 0x01;
static inline bool matchHeader(std::uint8_t byte) {
return (byte == headerVal);
}
Pkt1(std::uint8_t header): Pkt(header) {}
};
class Pkt2 : public Pkt {
public:
static inline const std::uint8_t headerVal = 0x02;
static inline bool matchHeader(std::uint8_t byte) {
return (byte == headerVal);
}
uint32_t pkt2Var;
Pkt2(std::uint8_t header): Pkt(header) { pkt2Var = 33; }
};
class PktGen {
private:
public:
std::queue<std::shared_ptr<Pkt>> pkts;
void genPkt(uint8_t header) {
if(Pkt1::matchHeader(header)) {
pkts.push(std::make_shared<Pkt1>(header));
}
if(Pkt2::matchHeader(header)) {
pkts.push(std::make_shared<Pkt2>(header));
}
}
std::queue<std::shared_ptr<Pkt>> getPkts() {
std::queue<std::shared_ptr<Pkt>> tmp;
tmp = pkts;
while(!pkts.empty()) {
pkts.pop();
}
return tmp;
}
};
There's probably something wrong with my getPkts() function, though it works here: https://godbolt.org/z/h8Y4qPKnh.
Anyway, my question is with LLDB: if I want to access:
p std::static_pointer_cast<std::shared_ptr<Pkt1>>(pkts.front())->pkt2Var
I get:
error: <user expression 42>:1:6: no template named 'static_pointer_cast' in namespace 'std'
How do I go about casting smart pointers in LLDB?
std::static_pointer_cast requires #include <memory>.
But in any case, your use of std::static_pointer_cast is just plain wrong anyway.
DON'T specify shared_ptr itself in the template parameter, only the pointer type being casted to, eg:
p std::static_pointer_cast<Pkt1>(pkts.front())->pkt2Var
However, Pkt1 does not have any pkt2Var member, but Pkt2 does:
p std::static_pointer_cast<Pkt2>(pkts.front())->pkt2Var
If the debugger still won't accept static_pointer_cast, then just use an ordinary static_cast instead (since you don't need the debugger taking shared ownership anyway):
p static_cast<Pkt2*>(pkts.front().get())->pkt2Var
I'm having a hard time understanding how to instantiate a ViewKit VkSubProcess object. Manpage for this thing is here: VkSubProcess.3
I have read up enough about C++ to understand that this header describes a reference counted abstract base class and I think I more-or-less understand the concepts involved. However, my feeble attempts at actually using this thing have been unsuccessful.
My efforts lead to various compiler errors like:
"The class "RPtr_VkSubProcessRep" has no member "create"
or:
"No instance of constructor 'VkSubProcessRep::VkSubProcessRep' matches the argument list"
Could somebody be kind enough to please show how to instantiate a VkSubProcess object from this header?
For brevity I've removed from the header the functions mentioned in the manpage that come into play only after the instance has been created.
Many thanks.
#ifndef _VKPROCESS_H
#define _VKPROCESS_H
#include <signal.h>
#include <Xm/Xm.h>
#include <Vk/VkCallbackObject.h>
#include <Vk/VkBase.h>
#include <Vk/VkApp.h>
class VkSPCounted : public VkCallbackObject {
friend class VkSPRPtr_base;
int nreferences;
void addRef() { nreferences++; }
void delRef() {
if (--nreferences <= 0)
delete this;
}
public:
VkSPCounted() : VkCallbackObject() { nreferences = 0; }
virtual ~VkSPCounted();
private:
VkSPCounted(const VkSPCounted&);
VkSPCounted &operator= (const VkSPCounted&);
};
class VkSPRPtr_base : public VkBase {
public:
operator void*() { return (void *)ptr; }
VkSPRPtr_base& operator=(VkSPCounted *tp) {
if (ptr) ptr->delRef();
ptr = tp;
if (ptr) ptr->addRef();
return *this;
}
VkSPRPtr_base& operator=(const VkSPRPtr_base& r){
if (ptr) ptr->delRef();
ptr = r.ptr;
if (ptr) ptr->addRef();
return *this;
}
protected:
VkSPCounted *ptr;
VkSPRPtr_base() : VkBase() { ptr = 0; }
VkSPRPtr_base(const VkSPRPtr_base& r) : VkBase() {
ptr = r.ptr;
if (ptr) ptr->addRef();
}
VkSPRPtr_base(VkSPCounted *tp) : VkBase() {
ptr = tp;
if (ptr) ptr->addRef();
}
~VkSPRPtr_base() {
if (ptr) ptr->delRef();
}
};
class VkSubProcessRep;
class RPtr_VkSubProcessRep : public VkSPRPtr_base {
public:
RPtr_VkSubProcessRep();
RPtr_VkSubProcessRep(VkSubProcessRep *tp);
RPtr_VkSubProcessRep(const RPtr_VkSubProcessRep& that) : VkSPRPtr_base(that) {}
~RPtr_VkSubProcessRep();
RPtr_VkSubProcessRep& operator=(VkSubProcessRep *tp) {
*((VkSPRPtr_base *) this) = (VkSPCounted *) tp;
return *this;
}
VkSubProcessRep& operator *();
VkSubProcessRep *operator->();
int operator !(){ return !ptr; }
};
typedef RPtr_VkSubProcessRep VkSubProcess;
class VkSubProcessRep : public VkSPCounted {
public:
static VkSubProcess create(char* cmd,
int killChildOnExit,
int redirectIn);
/* Actually create VkSubProcess */
void run();
protected:
VkSubProcessRep(const char* prog,
char **argv,
int killChildOnExit,
int redirectIn);
~VkSubProcessRep();
private:
VkSubProcessRep(const VkSubProcessRep&);
VkSubProcessRep &operator= (const VkSubProcessRep&);
};
#endif
Well, after spending the day reading up on C++, I now know that this header describes a Reference-counted Singleton pattern. From what I have read, the static 'create()' function and the protected constructor seem to be typical for this pattern.
You might use it like so:
VkSubProcess process = VkSubProcessRep::create("ls -al", 1, 1);
process->run();
I'm trying to define template class inside of nontemplate class, below you may see the code of what i'm actually trying to do (it doesn't compilable for obvious reason). The main question is how can I realize that using C++11 (preferable) or C++14?
Actually I've got solution using std::variant or same function from the BOOST library, but I need to know another way to solve that.
I found old similar question, and answer by Anne Quinn sounds valuable (he suggest to declare subclasses for each type I need), but how to apply that in code right?
Code:
#include <vector>
#include <cstdint>
enum Type {
INT16,
UINT32
};
template<typename T>
class Buffer {
public:
Buffer(uint32_t paramSize) {
buffer.resize(paramSize);
}
private:
std::vector<T> buffer;
};
class Foo {
public:
Foo(Type paramType, uint32_t paramSize) {
switch(paramType) {
case UINT32:
buffer = Buffer<uint32_t>(paramSize);
break;
case INT16:
buffer = Buffer<int16_t>(paramSize);
break;
}
}
private:
Buffer buffer;
};
int main() {
Foo var(INT16, 30);
return 0;
}
UPD1: answer by #user2308211 seems work, but I got two problems with that. In case I'm copying object Foo and original object destroys for some reason (moving out of scope for example), copy will stay with pointer to nowhere. Second one is how to retrieve my buffer through Foo class.
UPD2: shared_ptr resolves problem with copying, but then copy will store the same object, in case you wanna modify them independently, use copy constructor as shown in answer. As for access to original buffer, void pointer allow you to retrieve pointer to vector, then you should static_cast it to your type.
Thanks!
Have a base class with all the required functions for Buffer as pure virtual.
#include <vector>
#include <cstdint>
enum Type {
INT16,
UINT32
};
class BufferBase {
public:
virtual void someFn()=0;
virtual ~BufferBase() {}
};
template<typename T>
class Buffer:public BufferBase {
public:
Buffer(uint32_t paramSize) {
buffer.resize(paramSize);
}
void someFn() override {
//functionality.
}
~Buffer() {}
private:
std::vector<T> buffer;
};
class Foo {
public:
Foo(Type paramType, uint32_t paramSize) {
this->bufferType = paramType;
switch(paramType) {
case UINT32:
buffer = new Buffer<uint32_t>(paramSize);
break;
case INT16:
buffer = new Buffer<int16_t>(paramSize);
break;
}
}
~Foo() {
delete this->buffer;
}
Foo &operator=(const Foo &other) {
this->bufferType = other.bufferType;
switch(bufferType) {
case UINT32:
buffer = new Buffer<uint32_t>(*static_cast<Buffer<uint32_t>*>(other.buffer));
break;
case INT16:
buffer = new Buffer<int16_t>(*static_cast<Buffer<int16_t>*>(other.buffer));
break;
}
return *this;
}
Foo(const Foo &other) {
*this=other;
}
private:
BufferBase *buffer;
Type bufferType;
};
int main() {
Foo var1(INT16, 30), var2(UINT32, 25);
var1 = var2;
return 0;
}
EDIT: I've updated the answer with a copy constructor.
(For simplicity's sake, serializer will be called write and deserializer will be called read)
I'm writing a C++ game serializer from scratch with no library allowed.
The main concern i have is to keep the read and write in sync (the read values must be the same as the written ones). So the Packer handles both tasks and is specified with an enum.
What i have
enum PackerType {
WRITE,
READ
}
template <PackerType PType>
class Packer {
char *buffer; // Packer will write here
uint32_t index;
template <typename T>
void Pack(T & value); // Calls appropriate functions depending on PType
}
What i want to be able to do
class ElementToSerialize : ISeriablizable {
virtual void WriteAndRead(Packer & p) {
p.Pack(32);
p.Pack("Hello World");
}
}
Packer<WRITE> wpacker;
Packer<READ> rpacker;
rpacker.buffer = wpacker.buffer;
WriteAndRead(wpacker); // Will write everything in wpacker.buffer
WriteAndRead(rpacker); // Will read wpacker.buffer
So i know this is not possible in C++, but what i'm looking for is an elegant way of dealing with this issue. I'm already aware of type-erasure, but i'm not a fan of the solution.
How about starting with something like this:
class IPacker
{
public:
virtual void Pack(int& value) = 0;
virtual void Pack(float& value) = 0;
}
class ISeriablizable
{
public:
virtual void Serialize(IPacker & p) = 0;
}
class WritePacker : public IPacker
{
char* buffer;
int index = 0;
public:
WritePacker(char* buffer) : buffer(buffer) {}
void Pack(int& value) override { /* write to buffer */ }
void Pack(float& value) override { /* write to buffer */ }
}
class ReadPacker : public IPacker
{
char* buffer;
int index = 0;
public:
ReadPacker(char* buffer) : buffer(buffer) {}
void Pack(int& value) override { /* read from buffer */ }
void Pack(float& value) override { /* read from buffer */ }
}
class ElementToSerialize : public ISeriablizable
{
int x = 32;
std::string y = "Hello world";
void Serialize(IPacker & p) override
{
p.Pack(x);
p.Pack(y);
}
}
If it is not a requirement, you could avoid using templates altogether, and just store whether the Packer is read or write so you know it at runtime (example):
class Packer {
public:
enum Type {
READ,
WRITE
};
char *buffer; // Packer will write here
uint32_t index;
Type type;
Packer(Type t) : type(t) {}
void Pack(int& value);
void Pack(float& value);
void Pack(std::string& value);
// etc...
};
I have a problem. I've written a GPS module that can detect the type of the message on the fly and configure them if needed. I've done it by composition of several classes. To make code a little more independent from the platform (stm32) I created a IStreamDevice interface that has baic i/o operations. It works. Everything appers to be great, but the classs are apparently coupled. That't why I have several question:
How can I avoid the passing IStreamDevice to all devices?
How can I make the whole design more platform-independent (and os-independent)? We have plans to move to another OS in the nearest future. It is POSIX compliant. I think I will be able to implement my IStreamDevice interface there (the buses I can aend up using are UART and SPI. In my current version I use only UART). Am I wrong?
class IStreamDevice
{
public:
virtual ~IStreamDevice() {}
virtual uint32_t read(uint8_t* data, uint32_t size) = 0;
virtual uint32_t write(const uint8_t* data, uint32_t size) = 0;
virtual uint32_t bytesToRead() const = 0;
virtual uint32_t bytesToWrite() const = 0;
};
class GPSModule {
public:
GPSModule(periph::IStreamDevice *source);
~GPSModule();
void turnDevice1Messages();
void turnDevice2Messages();
void configureDevice1(...);
void configureDevice2(...);
void Scan();
private:
Device1Configurator *_device1_configurator;
Device2Configurator *_device2_configurator;
StreamDeviceScanner*_scanner;
periph::IStreamDevice *_source;
};
GPSModule::GPSModule(periph::IStreamDevice *source): _source(source)
{
_scanner= new StreamDeviceScanner(_source);
_device1_configurator = new Device1Configurator(_source);
_device2_configurator = new Device2Configurator(_source);
}
GPSModule::~GPSModule()
{
delete _scanner;
}
void GPSModule::Scan()
{
_scanner->Scan();
}
void GPSModule::turnDevice1Messages() {
_device1_configurator->turnMessages();
}
class StreamDeviceScanner{
public:
StreamDeviceScanner(periph::IStreamDevice *source);
~StreamDeviceScanner();
void Scan();
private:
typedef enum
{
WAITING_SYNC,
WAITING_DEVICE1_MSG,
WAITING_DEVICE2_MSG
} states_t;
periph::IStreamDevice *_source;
ProtocolScanner *_protocol_scanner;
states_t _state;
private:
states_t _catchSync();
uint32_t _read(uint8_t* data, uint32_t length) { return _source->read(data,length); }
uint32_t _bytesToRead() const { return _source->bytesToRead(); }
};
StreamDeviceScanner::StreamDeviceScanner(periph::IStreamDevice *source):
_source(source),
_state(WAITING_SYNC)
{
_protocol_scanner = new ProtocolScanner(source);
}
StreamDeviceScanner::~StreamDeviceScanner()
{
delete _protocol_scanner;
}
void StreamDeviceScanner::Scan()
{
while (_source->bytesToRead()) {
switch (_state)
{
case WAITING_SYNC:
_state = _catchSync();
break;
case WAITING_DEVICE1_MSG:
_protocol_scanner->Device1Scan()
_state = WAITING_SYNC;
break;
case WAITING_DEVICE2_MSG:
_protocol_scanner->Device2Scan()
_state = WAITING_SYNC;
break;
}
}
}
class ProtocolScanner {
private:
Device1Scanner *_Device1Scanner;
Device2Scanner *_Device2Scanner;
public:
ProtocolScanner(periph::IStreamDevice *source)
{
_Device1Scanner = new Device1Scanner(source);
_Device2Scanner = new Device2Scanner(source);
}
~ProtocolScanner()
{
delete _Device1Scanner;
delete _Device1Scanner;
}
bool Device1Scan() const { return _Device1Scanner->Scan(); }
bool Device2Scan() const { return _Device2Scanner->Scan(); }
};
class Device1Scanner {
public:
Device1Scanner(periph::IStreamDevice *source);
~Device1Scanner();
bool Scan();
private:
enum { BUFFER_LENGTH = 8192 };
typedef enum {
Waiting_Header,
Waiting_Payload,
Waiting_Checksum
} state_t;
uint8_t _buffer[BUFFER_LENGTH];
periph::IStreamDevice *_source;
state_t _state;
Device1Parser *_destination;
Device1Scanner::NovatelMessage _message;
private:
uint32_t _read(uint8_t* data, uint32_t size) { return _source->read(data,size); }
const uint32_t _bytesToRead() const { return _source->bytesToRead(); }
bool _receiveHeader();
bool _receivePayload();
bool _receiveChecksum();
bool _validChecksum() const;
};
Device2Scanner looks exactly the same. I'd like to hear everything that anyone has to say about the design.
I don't see any inherent problem with your design. Your IStreamWriter interface seems like a proper abstraction of the underlying bus, without being dependent on specific bus details. That complies with the Dependency Inversion principle and with design-by-contract approach. I also don't see tight coupling in your classes. You're accessing the bus via its handler, according to the interface specification, without dependency on the implementation of the actual bus handling class.
There is nothing platform dependent in the shown code. If the bus handling differs per platform, there is not much you can do except providing a different implementations for IStreamWriter according to platform.