在 C++ 中实现 Payload-containing class 的最佳实践?
Best practices to implement a Payload-containing class in C++?
我对层次结构、引用和指针有疑问...当我尝试执行以下操作时,我想到了这个问题:
class packet {
public:
int address;
int command; /**< Command select the type of Payload that I must decode */
Payload p; /**< Generic payload, first question:
Payload p or Payload * p or Payload &p ?
I have a background in C, for this reason I prefer
Payload p but I know that this is not recommended for C++ */
private:
/** All getter and setter for attributes */
/** Second question: What is the best way to implement a getter
and setter for Payload?... I prefer something
similar to Java if this is possible */
}
现在假设我有很多类型的有效载荷,所有这些有效载荷都是超级 class(通用)有效载荷的 children。
我想阅读 header 并关闭命令。例如,如果命令是 1 我创建一个 PayloadReset : Payload
并填写它的所有属性,那么我想 set 在我的数据包上这个有效载荷(up-casting) .在程序的其他部分,我想读取我当前的数据包,然后读取命令字段和 down-cast 到适当的类型,具体取决于命令字段。
当我尝试这样做时,我可以毫无问题地执行 up-casting,但是当我尝试向下转换到特定的有效负载时,在我们的示例 PayloadReset 中出现了问题。
回答第一个问题(隐藏在您第一个代码示例的注释中:
Payload *p;
在从 Java 过渡到 C++ 的过程中,您需要学习的第一件事是指针是什么以及它们如何工作。一段时间以来,您会感到困惑的是,Java 中的所有对象实际上都是指针。在使用 Java 时,您永远不需要知道这一点。但是你现在必须知道这一点,才能理解 C++。因此,将 C++ class 声明为
Payload p;
与Java中的类似声明不同。 Java 中没有与此声明等效的内容。在 Java 中你真的有一个指针,你必须使用 new
关键字实例化它。那部分 Java 最初是从 C++ 模仿而来的。这与 C++ 的过程相同,只是您必须将其显式声明为指针。
Payload *p;
然后,在其他地方,使用您的示例 PayloadReset
subclass:
class PayloadReset : public Payload { /* Class declaration */ };
PayloadReset *r = new PayloadReset( /* Constructor argument */ };
p=r;
作为从 Java 到 C++ 的交易的一部分,您需要学习的第二件事是何时以及如何 delete
所有实例化对象。您这里没有 Java 的垃圾收集器。现在这就是你的工作了。
您的问题与 Java 语法有些相关,但主要是关于面向对象编程。
首先,您应该花点时间熟悉 Java 命名约定。您可以在整个网络上找到常用的建议。这是 Java Naming Conventions 的一个示例。我之所以提出这个问题,是因为单个变量名通常不是一个好主意,并且随着程序规模的增长,使用描述性变量名会带来好处,尤其是当团队中有多个人时。所以,而不是 Payload p
使用 Payload payload
.
其次,在 OO(面向对象)中,最好始终保持 Class 实例变量私有,而不是 public。仅在必要时授予对这些变量的访问权限,并通过提供 public 方法来屏蔽对它们的访问。因此,在您的 class Packet
示例中,您的 public/private 是倒退的。您的 class 应该更像:
public class Packet{
//private fields
private int address;
private int command;
private Payload payload;
//Maybe provide a nice constructor to take in expected
//properties on instantiation
public Packet(Payload pay){
}
//public methods - as needed
public void getPayload(){
return this.payload;
}
public void setAddress(int addy){
this.address = addy;
}
public int getCommand(){
return this.command;
}
}
另外,多回答你关于Payload命名的问题。就像我之前说的……使用描述性名称。 Java 没有像 C 那样的指针引用,通常会为您处理内存管理,因此不需要或不支持 &
。
你的最后一个 question/topic 又是关于 OO 和 Class 层次结构的。
Payload 似乎是一个通用的基础 class,您可能有多个特定的 'Payload types',例如 ResetPayload
。如果是这种情况,您将定义 Payload
并创建扩展 Payload
的 ResetPayload
class。我不确定您到底想做什么,但可以将 Classes/objects 广告名词和方法视为动词。还要考虑 'is-a' 和 'has-a' 的概念。据我所知,也许所有 Payload
s 'has-acommand and an address. Also, maybe each
Payloadalso has multiple
Packets, whatever. Just as an example, you would then define your
Payload` class 像这样:
public class Payload{
private int address;
private int command;
private List<Packet> packets = new ArrayList<>();
public Payload(int addy, int comm){
this.address = addy;
this.command = comm;
}
public void addPacket(Packet p){
packets.add(p);
}
public List<Packet> getPackets(){
return this.packets;
}
public int getCommand(){
return this.command;
}
public int getAddress(){
return this.address;
}
}
然后,如果您有一个更具体的 Payload
类型,例如 Reset,您将创建 class、扩展 Payload
并提供额外的 properties/operations特定于此类型,例如:
public class ResetPayload extends Payload{
public ResetPayload(int addy, int comm){
super(addy, comm);
}
public void reset(){
//Do stuff here to reset the payload
}
}
希望这能回答您的问题并让您走得更远。祝你好运。
标记到 Sam 的回答上。
在继续之前,请了解堆栈和堆分配之间的区别。在您发布的示例中,您在堆栈上分配 Payload p;
object - 这意味着 object 的大小此时已知并且所述大小将在堆栈上分配.如果您想将派生的 object 分配给 p
,那是行不通的,因为 object 的大小可能不同。这就是为什么你改为声明一个指向 object 的指针(64 位架构上为 8 个字节,32 位架构上为 4 个字节),然后当你知道要分配哪种类型的派生 object 时,您使用 new
运算符来执行此操作,例如:
Payload *p;
p = new PayloadReset(...);
上述方法需要手动管理内存,即在 new
分配的指针上调用 delete
。从 C++11 开始,建议使用 <memory>
header 中的智能指针。这些本质上是引用计数指针,会自动为您调用 delete
。
std::shared_ptr<Payload> p;
p = std::make_shared<PayloadReset>(...);
这是我对一般问题的看法,它扩展了标记联合的想法。优点是 1.) 没有 inheritance/dynamic_cast 2.) 没有共享 ptr 3.) POD 4.) rtti 用于生成唯一标签:
using cleanup_fun_t = void(*)(msg*);
class msg
{
public:
template<typename T, typename... Args>
static msg make(Args&&... args);
private:
std::type_index tag_;
mutable std::atomic<cleanup_fun_t> del_fn_; // hell is waiting for me,
uint64_t meta_;
uint64_t data_;
};
请填写所有nice成员函数。这 class 只是移动。您正在通过静态成员函数 make
:
创建带有负载的消息
template<typename T, typename... Args>
msg msg::make(Args&&... args)
{
msg m;
m.tag_ = typeid(T);
m.del_fn_ = nullptr;
if (!(std::is_empty<T>::value))
{
auto ptr = std::make_unique<T>(std::forward<Args>(args)...);
m.data_ = (uint64_t)ptr.release();
m.del_fn_ = &details::cleanup_t<T>::fun; // deleter template not shown
}
return m;
}
// creation:
msg m = msg::make<Payload>(params passed to payload constructor);
// using
if (m.tag() == typeid(Payload))
{
Payload* ptr = (Payload*)m.data;
ptr-> ...
}
只需检查标签是否包含您期望的数据(类型)并将数据转换为指针类型。
免责声明:这不是完整的 class。此处缺少一些访问成员函数。
我对层次结构、引用和指针有疑问...当我尝试执行以下操作时,我想到了这个问题:
class packet {
public:
int address;
int command; /**< Command select the type of Payload that I must decode */
Payload p; /**< Generic payload, first question:
Payload p or Payload * p or Payload &p ?
I have a background in C, for this reason I prefer
Payload p but I know that this is not recommended for C++ */
private:
/** All getter and setter for attributes */
/** Second question: What is the best way to implement a getter
and setter for Payload?... I prefer something
similar to Java if this is possible */
}
现在假设我有很多类型的有效载荷,所有这些有效载荷都是超级 class(通用)有效载荷的 children。
我想阅读 header 并关闭命令。例如,如果命令是 1 我创建一个 PayloadReset : Payload
并填写它的所有属性,那么我想 set 在我的数据包上这个有效载荷(up-casting) .在程序的其他部分,我想读取我当前的数据包,然后读取命令字段和 down-cast 到适当的类型,具体取决于命令字段。
当我尝试这样做时,我可以毫无问题地执行 up-casting,但是当我尝试向下转换到特定的有效负载时,在我们的示例 PayloadReset 中出现了问题。
回答第一个问题(隐藏在您第一个代码示例的注释中:
Payload *p;
在从 Java 过渡到 C++ 的过程中,您需要学习的第一件事是指针是什么以及它们如何工作。一段时间以来,您会感到困惑的是,Java 中的所有对象实际上都是指针。在使用 Java 时,您永远不需要知道这一点。但是你现在必须知道这一点,才能理解 C++。因此,将 C++ class 声明为
Payload p;
与Java中的类似声明不同。 Java 中没有与此声明等效的内容。在 Java 中你真的有一个指针,你必须使用 new
关键字实例化它。那部分 Java 最初是从 C++ 模仿而来的。这与 C++ 的过程相同,只是您必须将其显式声明为指针。
Payload *p;
然后,在其他地方,使用您的示例 PayloadReset
subclass:
class PayloadReset : public Payload { /* Class declaration */ };
PayloadReset *r = new PayloadReset( /* Constructor argument */ };
p=r;
作为从 Java 到 C++ 的交易的一部分,您需要学习的第二件事是何时以及如何 delete
所有实例化对象。您这里没有 Java 的垃圾收集器。现在这就是你的工作了。
您的问题与 Java 语法有些相关,但主要是关于面向对象编程。
首先,您应该花点时间熟悉 Java 命名约定。您可以在整个网络上找到常用的建议。这是 Java Naming Conventions 的一个示例。我之所以提出这个问题,是因为单个变量名通常不是一个好主意,并且随着程序规模的增长,使用描述性变量名会带来好处,尤其是当团队中有多个人时。所以,而不是 Payload p
使用 Payload payload
.
其次,在 OO(面向对象)中,最好始终保持 Class 实例变量私有,而不是 public。仅在必要时授予对这些变量的访问权限,并通过提供 public 方法来屏蔽对它们的访问。因此,在您的 class Packet
示例中,您的 public/private 是倒退的。您的 class 应该更像:
public class Packet{
//private fields
private int address;
private int command;
private Payload payload;
//Maybe provide a nice constructor to take in expected
//properties on instantiation
public Packet(Payload pay){
}
//public methods - as needed
public void getPayload(){
return this.payload;
}
public void setAddress(int addy){
this.address = addy;
}
public int getCommand(){
return this.command;
}
}
另外,多回答你关于Payload命名的问题。就像我之前说的……使用描述性名称。 Java 没有像 C 那样的指针引用,通常会为您处理内存管理,因此不需要或不支持 &
。
你的最后一个 question/topic 又是关于 OO 和 Class 层次结构的。
Payload 似乎是一个通用的基础 class,您可能有多个特定的 'Payload types',例如 ResetPayload
。如果是这种情况,您将定义 Payload
并创建扩展 Payload
的 ResetPayload
class。我不确定您到底想做什么,但可以将 Classes/objects 广告名词和方法视为动词。还要考虑 'is-a' 和 'has-a' 的概念。据我所知,也许所有 Payload
s 'has-acommand and an address. Also, maybe each
Payloadalso has multiple
Packets, whatever. Just as an example, you would then define your
Payload` class 像这样:
public class Payload{
private int address;
private int command;
private List<Packet> packets = new ArrayList<>();
public Payload(int addy, int comm){
this.address = addy;
this.command = comm;
}
public void addPacket(Packet p){
packets.add(p);
}
public List<Packet> getPackets(){
return this.packets;
}
public int getCommand(){
return this.command;
}
public int getAddress(){
return this.address;
}
}
然后,如果您有一个更具体的 Payload
类型,例如 Reset,您将创建 class、扩展 Payload
并提供额外的 properties/operations特定于此类型,例如:
public class ResetPayload extends Payload{
public ResetPayload(int addy, int comm){
super(addy, comm);
}
public void reset(){
//Do stuff here to reset the payload
}
}
希望这能回答您的问题并让您走得更远。祝你好运。
标记到 Sam 的回答上。
在继续之前,请了解堆栈和堆分配之间的区别。在您发布的示例中,您在堆栈上分配
Payload p;
object - 这意味着 object 的大小此时已知并且所述大小将在堆栈上分配.如果您想将派生的 object 分配给p
,那是行不通的,因为 object 的大小可能不同。这就是为什么你改为声明一个指向 object 的指针(64 位架构上为 8 个字节,32 位架构上为 4 个字节),然后当你知道要分配哪种类型的派生 object 时,您使用new
运算符来执行此操作,例如:Payload *p; p = new PayloadReset(...);
上述方法需要手动管理内存,即在
new
分配的指针上调用delete
。从 C++11 开始,建议使用<memory>
header 中的智能指针。这些本质上是引用计数指针,会自动为您调用delete
。std::shared_ptr<Payload> p; p = std::make_shared<PayloadReset>(...);
这是我对一般问题的看法,它扩展了标记联合的想法。优点是 1.) 没有 inheritance/dynamic_cast 2.) 没有共享 ptr 3.) POD 4.) rtti 用于生成唯一标签:
using cleanup_fun_t = void(*)(msg*);
class msg
{
public:
template<typename T, typename... Args>
static msg make(Args&&... args);
private:
std::type_index tag_;
mutable std::atomic<cleanup_fun_t> del_fn_; // hell is waiting for me,
uint64_t meta_;
uint64_t data_;
};
请填写所有nice成员函数。这 class 只是移动。您正在通过静态成员函数 make
:
template<typename T, typename... Args>
msg msg::make(Args&&... args)
{
msg m;
m.tag_ = typeid(T);
m.del_fn_ = nullptr;
if (!(std::is_empty<T>::value))
{
auto ptr = std::make_unique<T>(std::forward<Args>(args)...);
m.data_ = (uint64_t)ptr.release();
m.del_fn_ = &details::cleanup_t<T>::fun; // deleter template not shown
}
return m;
}
// creation:
msg m = msg::make<Payload>(params passed to payload constructor);
// using
if (m.tag() == typeid(Payload))
{
Payload* ptr = (Payload*)m.data;
ptr-> ...
}
只需检查标签是否包含您期望的数据(类型)并将数据转换为指针类型。
免责声明:这不是完整的 class。此处缺少一些访问成员函数。