[api] Make ProtoDecodableMessage::decode() non-virtual

decode() is never called polymorphically - all call sites in
read_message_() use concrete types. The only indirect call site was
decode_to_message(), which also always knows the concrete type.

Convert decode_to_message() to a template so the concrete type is
preserved, allowing decode() to be non-virtual. The two classes that
override decode() (ExecuteServiceArgument, ExecuteServiceRequest) now
hide the base method, which works since all calls use concrete types.

This removes one vtable slot (4 bytes) from each decodable message
class vtable, saving ~148 bytes of flash.
This commit is contained in:
J. Nick Koston
2026-03-21 20:51:28 -10:00
parent 1d191d2461
commit a5bd718fc9
3 changed files with 10 additions and 18 deletions

View File

@@ -1253,7 +1253,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage {
FixedVector<int32_t> int_array{};
FixedVector<float> float_array{};
FixedVector<std::string> string_array{};
void decode(const uint8_t *buffer, size_t length) override;
void decode(const uint8_t *buffer, size_t length);
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1278,7 +1278,7 @@ class ExecuteServiceRequest final : public ProtoDecodableMessage {
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
bool return_response{false};
#endif
void decode(const uint8_t *buffer, size_t length) override;
void decode(const uint8_t *buffer, size_t length);
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif

View File

@@ -152,8 +152,7 @@ class ProtoVarInt {
#endif
};
// Forward declarations for decode_to_message and related encoding helpers
class ProtoDecodableMessage;
// Forward declarations for encoding helpers
class ProtoMessage;
class ProtoSize;
@@ -166,16 +165,9 @@ class ProtoLengthDelimited {
const uint8_t *data() const { return this->value_; }
size_t size() const { return this->length_; }
/**
* Decode the length-delimited data into an existing ProtoDecodableMessage instance.
*
* This method allows decoding without templates, enabling use in contexts
* where the message type is not known at compile time. The ProtoDecodableMessage's
* decode() method will be called with the raw data and length.
*
* @param msg The ProtoDecodableMessage instance to decode into
*/
void decode_to_message(ProtoDecodableMessage &msg) const;
/// Decode the length-delimited data into a message instance.
/// Template preserves concrete type so decode() resolves statically.
template<typename T> void decode_to_message(T &msg) const;
protected:
const uint8_t *const value_;
@@ -454,7 +446,7 @@ class ProtoMessage {
// Base class for messages that support decoding
class ProtoDecodableMessage : public ProtoMessage {
public:
virtual void decode(const uint8_t *buffer, size_t length);
void decode(const uint8_t *buffer, size_t length);
/**
* Count occurrences of a repeated field in a protobuf buffer.
@@ -690,8 +682,8 @@ template<typename T> inline void ProtoWriteBuffer::encode_optional_sub_message(u
this->encode_optional_sub_message(field_id, value.calculate_size(), &value, &proto_encode_msg<T>);
}
// Implementation of decode_to_message - must be after ProtoDecodableMessage is defined
inline void ProtoLengthDelimited::decode_to_message(ProtoDecodableMessage &msg) const {
// Template decode_to_message - preserves concrete type so decode() resolves statically
template<typename T> void ProtoLengthDelimited::decode_to_message(T &msg) const {
msg.decode(this->value_, this->length_);
}

View File

@@ -2262,7 +2262,7 @@ def build_message_type(
o += "}\n"
cpp += o
# Generate the decode() declaration in header (public method)
prot = "void decode(const uint8_t *buffer, size_t length) override;"
prot = "void decode(const uint8_t *buffer, size_t length);"
public_content.append(prot)
# Only generate encode method if this message needs encoding and has fields