nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
rclcpp::Publisher< M > Class Template Reference

#include <publisher.hpp>

Classes

class  Loan

Public Types

using ConstSharedPtr = ::rclcpp::Owned<Publisher<M>>
using SharedPtr = ::rclcpp::Owned<Publisher<M>>
using UniquePtr = ::rclcpp::Owned<Publisher<M>>

Public Member Functions

Result assert_liveliness ()
::rclcpp::QoS get_actual_qos () const
Result get_gid (uint8_t(&out_gid)[GID_SIZE]) const
Result get_subscription_count (::rclcpp::Executor &executor, size_t *out_count) const
const char * get_topic_name () const
 Get the topic name.
bool is_durability_transient_local () const
bool is_valid () const
 Check if the publisher is initialized and valid.
::rclcpp::ResultOf< Loan > loan (size_t requested_len)
Result on_incompatible_type (IncompatibleTypeCallbackType cb, void *user_context=nullptr)
Result on_liveliness_lost (nros_cpp_publisher_count_cb_t cb, void *user_context=nullptr)
Result on_matched (PublisherMatchedCallbackType cb, void *user_context=nullptr)
Result on_offered_deadline_missed (uint32_t deadline_ms, nros_cpp_publisher_count_cb_t cb, void *user_context=nullptr)
Publisher & operator= (Publisher &&other)
Result publish (const M &msg)
Result publish_raw (const uint8_t *data, size_t len)
 Renamed to publish_serialized (phase-515 C8); kept one release.
Result publish_serialized (const uint8_t *data, size_t len)
template<typename W>
Result publish_streamed (size_t total_len, W &&writer)
 Publisher ()
 Publisher (Publisher &&other)
 ~Publisher ()
 Destructor — releases publisher resources.

Static Public Attributes

static constexpr size_t GID_SIZE = 24

Friends

class ::rclcpp::Node

Detailed Description

template<typename M>
class rclcpp::Publisher< M >

Typed publisher for a ROS 2 topic.

Mirrors upstream's rclcpp::Publisher<M>. The message type M must provide TYPE_NAME, TYPE_HASH, and ffi_publish() (generated by codegen).

Serialized (CDR) publishing is always available via publish_serialized().

Inline storage holds the runtime publisher handle directly (NROS_PUBLISHER_SIZE bytes; size auto-derived from the runtime) plus one pointer: the contracted endpoint's monitor cell (phase-462 W1, RFC-0052), which the runtime resolves at create time against the table the entry installed with nros_cpp_install_monitors and bumps on every publish, exactly as the Rust EmbeddedPublisher does. Null on an uncontracted publisher. Topic name metadata lives C++-side in topic_name_, avoiding a runtime hop for get_topic_name().

WHY THIS ONE IS NOT AN ARENA HANDLE — phase-456 W4

Every other entity in this API is moving to a two-word handle over the Rust executor arena, because the arena already owns the entity and the C++ object was a second copy of state. A publisher is the exception, decided rather than left over.

The arena holds only things the executor DISPATCHES to — its EntryKind is subscription, service, client, timer, action server, action client, guard condition — and a publisher is none of them. The Rust API says the same by construction: Node::create_publisher_with_qos hands back an EmbeddedPublisher<M> BY VALUE, a caller-owned value with a Drop. So the phase's principle, that entity lifetime is a Rust data structure's, is already satisfied here, and rclcpp::Owned<Publisher<M>> mirrors that structure exactly.

It was also argued that it could not be moved without losing something — the arena then had no removal path, so an arena publisher could never have been destroyed before its executor. 9768795b1d (issue 1496) retired that argument; the one above, that an arena slot would make C++ diverge from the Rust lifetime, is what carries the decision.

Measured, so the question does not get re-opened on a size argument: sizeof(Publisher<M>) is 872 bytes and INDEPENDENT of M — identical for a 512-byte-bound string, an 8-byte Int32 and a 65 552-byte image. It is storage_ 608 + topic_name_ 256 + initialized_ + padding. Nothing here is a transmit buffer sized from the type's bound: serialisation goes into the backend's outbound buffer, and publish_streamed stages on the stack. That is what separates this type from the 888-byte Subscription<M> whose storage_ went unused on the dispatch path, and from the 4 672-byte Client<int> that carried a reply buffer.

Usage:

RCLCPP_TRY(node.create_publisher(pub, "/chatter"));
pub.publish_serialized(cdr_data, cdr_len);
Definition publisher.hpp:178
Result publish_serialized(const uint8_t *data, size_t len)
Definition publisher.hpp:282
#define RCLCPP_TRY(expr)
Definition result.hpp:259

Member Typedef Documentation

◆ ConstSharedPtr

template<typename M>
using rclcpp::Publisher< M >::ConstSharedPtr = ::rclcpp::Owned<Publisher<M>>

rclcpp::Publisher<M>::ConstSharedPtr — the SAME type as SharedPtr.

Owned<const T> is refused by a static_assert in owned.hpp: it declares cleanly and is ill-formed on the first move or reset(), which is worse than a refusal. A const/mutable distinction drawn over a handle presupposes shared ownership, which a sole owner does not have; const SharedPtr& already yields the const Publisher<M>* view.

◆ SharedPtr

template<typename M>
using rclcpp::Publisher< M >::SharedPtr = ::rclcpp::Owned<Publisher<M>>

rclcpp::Publisher<M>::SharedPtr — phase-456 W5.

rclcpp::Publisher<M>::SharedPtr member_; is close to universal in ported source, so this alias must exist on EVERY target — which std::shared_ptr does not. It names rclcpp::Owned<Publisher<M>>: the publisher BY VALUE, with operator-> so every ported pub_->publish(m) keeps working, and no allocator, control block or <memory> anywhere.

IT IS NOT AN ARENA HANDLE, and that is a decision rather than an omission — W4 measured the case and refused it. It was first argued from the arena having no removal path (EntryKind has no publisher at all), so that ~Publisher() and Owned<T>::reset() would become no-ops; that argument expired with 9768795b1d (issue 1496). The Rust side is what settles it: create_publisher_with_qos returns an EmbeddedPublisher<M> BY VALUE, with a Drop, and this phase's principle is that the C++ lifetime mirrors the Rust one. owned.hpp carries the long form.

WHAT CHANGED FOR A PORTED FILE: one reference, not a shared count. The entity is destroyed when the holder is, which is what upstream does when the holder was the last owner — and the W0 census found no entity handle copied anywhere in this tree or the porting corpus.

◆ UniquePtr

template<typename M>
using rclcpp::Publisher< M >::UniquePtr = ::rclcpp::Owned<Publisher<M>>

rclcpp::Publisher<M>::UniquePtr — also the SAME type as SharedPtr.

Owned<T> IS unique ownership — move-only, one owner, destroys on scope exit — so a separate alias would be a second spelling of one type. Measured before the collapse (phase-456 W5): zero uses in the tree outside this definition and the one probe asserting its old shape.

Constructor & Destructor Documentation

◆ ~Publisher()

template<typename M>
rclcpp::Publisher< M >::~Publisher ( )
inline

Destructor — releases publisher resources.

◆ Publisher() [1/2]

template<typename M>
rclcpp::Publisher< M >::Publisher ( Publisher< M > && other)
inline

◆ Publisher() [2/2]

template<typename M>
rclcpp::Publisher< M >::Publisher ( )
inline

Default constructor — creates an uninitialized publisher. Use Node::create_publisher() to initialize.

Member Function Documentation

◆ assert_liveliness()

template<typename M>
Result rclcpp::Publisher< M >::assert_liveliness ( )
inline

Phase 108.B.7 — manually assert liveliness. Required for publishers configured with LivelinessPolicy::ManualByTopic / ManualByNode; nothing to assert for Automatic / None, which answer Ok on every backend.

ErrorCode::Unsupported under a manual kind on a backend with no per-topic lease — zenoh-pico, XRCE-DDS, uORB (the phase-467 RMW gap-closure design study's Row 7). The entity is fine; the assertion simply did not reach a peer, which is the one thing a caller uses this for. cyclonedds performs it for real.

◆ get_actual_qos()

template<typename M>
::rclcpp::QoS rclcpp::Publisher< M >::get_actual_qos ( ) const
inline

The QoS profile this publisher is ACTUALLY running — issue 1437.

rclcpp::Publisher::get_actual_qos: what the BACKEND GRANTED, which differs from what create_publisher was handed whenever the backend serves something else — zenoh clamps history depth to its receive ring, DDS negotiates, and a launch-lowered QoS override has already been folded in before either. That difference is what answers "why is nothing arriving": a RELIABLE reader does not match a BEST_EFFORT writer, and a clamped depth drops samples the caller believed kept.

Per policy, and a policy the backend cannot report is an ABSENCE — ReliabilityPolicy::Unknown, DurabilityPolicy::Unknown, ... — not your request echoed back. A backend with no read-back at all (XRCE) answers Unknown in every field. So compare field by field and check for the sentinel first; agreement you did not check for may be silence.

Free: read once at create and retained on the handle, so this never re-enters the transport and is safe from a callback. An uninitialized publisher answers the all-absent profile.

◆ get_gid()

template<typename M>
Result rclcpp::Publisher< M >::get_gid ( uint8_t(&) out_gid[GID_SIZE]) const
inline

This publisher's own global identifier — rclcpp::PublisherBase::get_gid.

The phase-467 RMW gap-closure design study's Q1. Fills out_gid with [GID_SIZE](GID_SIZE) bytes on Ok, and writes nothing otherwise.

Upstream returns const rmw_gid_t & and cannot fail. Ours can — a backend may have no identity for this publisher — and RFC-0018 leaves no exceptions to say so with, so the value leaves through a reference to a caller-placed array and the verdict through the Result. An array REFERENCE rather than a pointer is what makes the width a compile-time fact at every call site.

Three bounds, each of which a caller will otherwise assume away:

  • How many of the bytes MEAN anything is a backend property. A backend whose identity is narrower zero-extends into the tail, so two gids naming one entity compare equal. Gids produced by two different backends in one image are never comparable.
  • Comparable with the gid on a RECEIVED sample on zenoh and Cyclone, which derive both from one source (issue 1495): a subscriber's MessageInfo names this publisher by these bytes.
  • An all-zero gid is never handed back as an answer. A backend with no identity for this publisher reports ErrorCode::Unsupported, because all-zero is what an unwritten buffer holds and the two must not read alike.

◆ get_subscription_count()

template<typename M>
Result rclcpp::Publisher< M >::get_subscription_count ( ::rclcpp::Executor & executor,
size_t * out_count ) const
inline

How many subscriptions are on this publisher's topic, RIGHT NOW — rclcpp's Publisher::get_subscription_count. phase-444.

A WEAKER QUESTION THAN UPSTREAM'S, and it is stated here rather than discovered. Upstream counts the subscriptions MATCHED to THIS publisher — peers whose QoS is compatible with it. This counts every subscription DISCOVERED on the topic, matched or not, because that is what the graph can answer and a backend with no QoS negotiation cannot tell the two apart at all.

Non-inverting, and in the safe direction: a topic nobody subscribes to still reads 0, so the use this exists for — skip the work when nobody is listening — is answered exactly. An INCOMPATIBLE peer makes it read 1 where upstream reads 0, i.e. you do work nobody receives. Never the reverse.

Takes the executor and an out-parameter, neither of which upstream does. The executor because we open ONE transport session per image and it owns the session, so the graph is the executor's to read — the same receiver difference as every Executor::get_* graph verb (phase-381 W4). The out-parameter because RFC-0018 forbids exceptions, so the error has to be the return value. Both weakenings are ones the compiler makes a porter notice, which is RFC-0089's mechanical-edit test.

Reports what has been DISCOVERED and never blocks, so a small answer right after startup is "not seen yet", never "not there"; a backend that cannot read the graph returns an error, which is a DIFFERENT answer from 0 and leaves *out_count untouched.

◆ get_topic_name()

template<typename M>
const char * rclcpp::Publisher< M >::get_topic_name ( ) const
inline

Get the topic name.

◆ is_durability_transient_local()

template<typename M>
bool rclcpp::Publisher< M >::is_durability_transient_local ( ) const
inline

rclcpp::PublisherBase::is_durability_transient_local() (Jazzy) – whether this publisher keeps samples for late-joining subscribers. issue 1975.

Read off get_actual_qos(), as upstream reads it off rcl_publisher_get_actual_qos: the GRANTED durability, not the requested one. Its envelope is that accessor's: a backend that cannot report durability (XRCE) answers DurabilityPolicy::Unknown, and Unknown is not TRANSIENT_LOCAL, so this reads false there – an absence, never your request echoed back. An uninitialized publisher answers false.

◆ is_valid()

template<typename M>
bool rclcpp::Publisher< M >::is_valid ( ) const
inline

Check if the publisher is initialized and valid.

◆ loan()

template<typename M>
::rclcpp::ResultOf< Loan > rclcpp::Publisher< M >::loan ( size_t requested_len)
inline

Loan a writable slot of at least requested_len bytes. The returned Loan is RAII: call commit(actual_len) to send, discard() to abandon, or let it drop (auto-discard).

Zero-copy only where the backend lends, which is zenoh alone; on Cyclone, XRCE and uORB this returns an error, permanently and in every build (issue 0814) — publish with publish() or publish_streamed() there.

◆ on_incompatible_type()

template<typename M>
Result rclcpp::Publisher< M >::on_incompatible_type ( IncompatibleTypeCallbackType cb,
void * user_context = nullptr )
inline

Issue 1972 — rclcpp's PublisherEventCallbacks::incompatible_type_callback: a subscription on this topic NAME with a different type was seen.

◆ on_liveliness_lost()

template<typename M>
Result rclcpp::Publisher< M >::on_liveliness_lost ( nros_cpp_publisher_count_cb_t cb,
void * user_context = nullptr )
inline

Register a callback for liveliness-lost events on this publisher — rclcpp's PublisherEventCallbacks::liveliness_callback (issue 1993). cb(storage, count status, user_context), observed on this publisher's poll points (publish, assert_liveliness()). ErrorCode::Unsupported on a backend that does not generate the event (XRCE-DDS, uORB); ErrorCode::Full when the process-wide event-slot table (16 registrations, C and C++ together) is full.

◆ on_matched()

template<typename M>
Result rclcpp::Publisher< M >::on_matched ( PublisherMatchedCallbackType cb,
void * user_context = nullptr )
inline

Issue 1972 — rclcpp's PublisherEventCallbacks::matched_callback: cb(storage, MatchedInfo, user_context) when a subscription starts or stops matching this publisher.

Observed on this publisher's poll points — publish and assert_liveliness() — on both backends that carry it (Cyclone DDS, zenoh); a publisher waiting for a listener calls assert_liveliness() in its wait loop. ErrorCode::Unsupported on XRCE-DDS and uORB. Registering again replaces the earlier callback; the registration follows the publisher across a move.

◆ on_offered_deadline_missed()

template<typename M>
Result rclcpp::Publisher< M >::on_offered_deadline_missed ( uint32_t deadline_ms,
nros_cpp_publisher_count_cb_t cb,
void * user_context = nullptr )
inline

Register a callback for offered-deadline-missed events on this publisher — rclcpp's PublisherEventCallbacks::deadline_callback. deadline_ms reaches the backend's registration: zenoh uses it when the QoS states no deadline, Cyclone DDS takes the QoS deadline. Same returns as on_liveliness_lost.

◆ operator=()

template<typename M>
Publisher & rclcpp::Publisher< M >::operator= ( Publisher< M > && other)
inline

◆ publish()

template<typename M>
Result rclcpp::Publisher< M >::publish ( const M & msg)
inline

Publish a typed message.

Calls the codegen-generated M::ffi_publish() which serializes the message to CDR and publishes it.

phase-417 stage 3 — the initialized_ guard. publish was the ONLY entry point on this class without one, and the one that matters most: M::ffi_publish reaches nros_cpp_publish_raw, whose only check is storage.is_null(), and storage_ is an in-object array that is never null. So an uninitialised publisher reinterpreted zero-initialised bytes as an RmwPublisher — silently wrong, then UB, with no diagnostic at any warning level. That is reachable straight from create_publisher, which returns a Result a ported node may not read and leaves out untouched on failure.

Returns Result, where upstream returns void and throws: no exceptions here (RFC-0018). An uninitialised publisher answers NotInitialized and sends nothing. In freestanding code Result is [[nodiscard]], so a dropped one warns. On the std surface (ported code) the result may be dropped, as upstream's void is, and a failure is logged instead (detail::PublishResult, phase-488).

◆ publish_raw()

template<typename M>
Result rclcpp::Publisher< M >::publish_raw ( const uint8_t * data,
size_t len )
inline

Renamed to publish_serialized (phase-515 C8); kept one release.

◆ publish_serialized()

template<typename M>
Result rclcpp::Publisher< M >::publish_serialized ( const uint8_t * data,
size_t len )
inline

Publish CDR bytes — rclcpp's publish(const SerializedMessage&).

phase-515 C8: renamed from publish_raw, so the transmit verb reads as the receive side's take_serialized does. ADOPT-BOUNDED: the payload is a (data, len) pair, not an rclcpp::SerializedMessage, which owns a growable buffer through an allocator this API does not have.

◆ publish_streamed()

template<typename M>
template<typename W>
Result rclcpp::Publisher< M >::publish_streamed ( size_t total_len,
W && writer )
inline

Phase 124.E.1 — streamed publish.

total_len declares the full payload size; writer is invoked one or more times with a writable chunk and must return the bytes written. The runtime/backend allocates total_len bytes (in the outbound buffer when the backend natively streams, otherwise in a stack staging buffer capped at ~4 KiB) and drains the closure until full.

writer signature: size_t(uint8_t* chunk, size_t cap).

◆ ::rclcpp::Node

template<typename M>
friend class ::rclcpp::Node
friend

Member Data Documentation

◆ GID_SIZE

template<typename M>
size_t rclcpp::Publisher< M >::GID_SIZE = 24
staticconstexpr

Width of the buffer get_gid() fills — upstream's RMW_GID_STORAGE_SIZE, the phase-467 RMW gap-closure design study's Q1(a). Named here so a call site spells the size once and the compiler checks it, rather than copying a 24 that has already moved once.


The documentation for this class was generated from the following files: