nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
Classes | Public Types | Public Member Functions | Static Public Attributes | Friends | List of all members
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_valid () const
 Check if the publisher is initialized and valid.
 
::rclcpp::ResultOf< Loan > loan (size_t requested_len)
 
Result on_liveliness_lost (nros_cpp_publisher_count_cb_t cb, void *user_context=nullptr)
 Register a callback for liveliness-lost events on this publisher.
 
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)
 Publish raw CDR bytes.
 
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).

Raw CDR publishing is always available via publish_raw().

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_raw(cdr_data, cdr_len);
Definition client.hpp:129
#define RCLCPP_TRY(expr)
Definition result.hpp:258

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 LivelinessManualByTopic / 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_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).

◆ 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.

◆ 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.

◆ 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). Result is [[nodiscard]], so a ported pub->publish(msg); warns; an uninitialised publisher answers NotInitialized and sends nothing.

◆ publish_raw()

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

Publish raw CDR bytes.

◆ 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).

Friends And Related Symbol Documentation

◆ ::rclcpp::Node

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

Member Data Documentation

◆ GID_SIZE

template<typename M >
constexpr 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: