'Inherit from std::default_delete for specialisation

Background: I am currently trying to improve a project of mine: I want to make a specific camera usable with a framework. The camera is accessible via a proprietary SDK (Pylon). The framework is ROS2.

Now for the challenge: As memory bandwidth is a bottle-neck in my use case, I want to avoid copying image data at all costs. The camera SDK provides a mechanism where the user-defined buffer is allocated only once. Buffer is a native ROS2 type called sensor_msgs::msg::Image (please note: There is no std::unique_ptr involved here). The camera is writing into it asynchronously. This is working fine.

ROS2 provides a zero-copy mechanism to implicitly share memory between components using a std::unique_ptr<sensor_msgs::msg::Image> in publish. This is also working fine.

Problem: The mechanism assumes you std::move the std::unique_ptr away from the sender. This is totally reasonable for the default use-case. After the data has been transferred, std::unique_ptr calls the deleter which then de-allocates the transferred object. Unfortunately, that still is the buffer the camera driver wants to write to.

The first thing I tried was wrapping the "static" object into a std::unique_ptr with a no-op deleter using a lambda:

auto deleter = [](sensor_msgs::msg::Image*){ std::cerr << "NoDeleter is not deleting an Image." << std::endl; };
std::unique_ptr<sensor_msgs::msg::Image, decltype(deleter)> img_msg_ptr(img_msg, deleter);

But the types are incompatible:

no known conversion for argument 1 from ‘unique_ptr<[...],…::<lambda(sensor_msgs::msg::Image_<std::allocator<void> >*)>>’ to ‘unique_ptr<[...],std::default_delete<sensor_msgs::msg::Image_<std::allocator<void> > >

It looks like the deleter must inherit from std::default_delete. sensor_msgs::msg::Image is a shorthand for sensor_msgs::msg::Image_<std::allocator<void> >.

Next I tried inheriting from std::default_delete:

class NoDeleter : public std::default_delete<sensor_msgs::msg::Image> {
    public:
    void operator()(sensor_msgs::msg::Image *) const override {
        std::cerr << "NoDeleter is not deleting an Image." << std::endl;
    }
};

But gcc tells me I am doing it wrong:

error: ‘void NoDeleter::operator()(sensor_msgs::msg::Image*) const’ marked ‘override’, but does not override

I tried specializing std::default_delete, but it had no effect. I assume, I am using the wrong type.

How can I (manually) determine the type I need to use?

Update: As requested, here is a complete example (it depends on ROS2 "foxy"):

staticbufferpublisher.cpp:

#include <rclcpp/rclcpp.hpp>
#include <rclcpp_components/register_node_macro.hpp>
#include <sensor_msgs/msg/image.hpp>
#include <chrono> // for 1s literal
using namespace std::chrono_literals;
class StaticBufferPublisher : public rclcpp::Node {
private:
    rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr imagePublisher;
    rclcpp::TimerBase::SharedPtr timer;
    sensor_msgs::msg::Image buffer;
public:
    StaticBufferPublisher(const rclcpp::NodeOptions & options) : rclcpp::Node("StaticBufferPublisher", options) {
        if (!options.use_intra_process_comms()) {
            throw std::runtime_error("Must use use_intra_process_comms!");
        }
        imagePublisher = this->create_publisher<sensor_msgs::msg::Image>("image", 1);
        buffer.data.resize(1024); // prepare buffer once
        // camera.set_buffer(buffer.data.data()) // tell camera where to put the data
        // camera.start_grabbing() // start camera
        // this timer simulates a while(camera.block_until_image_ready())
        timer = this->create_wall_timer(1s, [this]() -> void {
            std::cerr << "Sending  data starting at " << static_cast<void*>(buffer.data.data()) << "." << std::endl;
            /*
             * THE INTERESTING PART IS HERE
             */
            imagePublisher->publish(buffer); // this works, but creates a copy
            std::unique_ptr<sensor_msgs::msg::Image> img_msg_ptr(&buffer);
            imagePublisher->publish(std::move(img_msg_ptr)); // this does not create a copy, but releases the buffer – so it "works", but only once
            //auto deleter = [](sensor_msgs::msg::Image*){ std::cerr << "NoDeleter is not deleting an Image." << std::endl; };
            //std::unique_ptr<sensor_msgs::msg::Image, decltype(deleter)> img_msg_ptr(img_msg, deleter); // publish() will not accept this unique_ptr
        });
    }
};
RCLCPP_COMPONENTS_REGISTER_NODE(StaticBufferPublisher)

addressprinter.cpp:

#include <rclcpp/rclcpp.hpp>
#include <rclcpp_components/register_node_macro.hpp>
#include <sensor_msgs/msg/image.hpp>
class AddressPrinter : public rclcpp::Node {
private:
    rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr imageSubscription;
public:
    AddressPrinter(const rclcpp::NodeOptions & options) : rclcpp::Node("AddressPrinter", options) {
        if (!options.use_intra_process_comms()) {
            throw std::runtime_error("Must use use_intra_process_comms!");
        }
        imageSubscription = this->create_subscription<sensor_msgs::msg::Image>("image", 1,
            [this](sensor_msgs::msg::Image::UniquePtr msg) {
                std::cerr << "Received data starting at " << static_cast<void*>(msg->data.data()) << "." << std::endl;
            }
        );
    }
};
RCLCPP_COMPONENTS_REGISTER_NODE(AddressPrinter)

Launchfile:

import launch
from launch_ros.actions import ComposableNodeContainer
from launch_ros.descriptions import ComposableNode
def generate_launch_description():
    image_processing = ComposableNodeContainer(
            name = 'container',
            namespace = '',
            package = 'rclcpp_components',
            executable = 'component_container',
            composable_node_descriptions = [
                ComposableNode(
                    name = 'address_printer',
                    namespace = '',
                    package = 'pylon_instant_camera',
                    plugin = 'AddressPrinter',
                    extra_arguments=[{'use_intra_process_comms': True}]
                ),
                ComposableNode(
                    name = 'publisher',
                    namespace = '',
                    package = 'pylon_instant_camera',
                    plugin = 'StaticBufferPublisher',
                    extra_arguments=[{'use_intra_process_comms': True}]
                )
            ],
            output = 'screen'
    )
    return launch.LaunchDescription([image_processing])

CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(pylon_instant_camera)
set (CMAKE_CXX_STANDARD 17)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_components REQUIRED)
add_library(AddressPrinter SHARED src/addressprinter.cpp)
ament_target_dependencies(AddressPrinter 
    rclcpp
    rclcpp_components
    sensor_msgs
)
rclcpp_components_register_node(AddressPrinter PLUGIN "AddressPrinter" EXECUTABLE "addressprinter")
add_library(StaticBufferPublisher SHARED src/staticbufferpublisher.cpp)
ament_target_dependencies(StaticBufferPublisher 
    rclcpp
    rclcpp_components
    sensor_msgs
)
rclcpp_components_register_node(StaticBufferPublisher PLUGIN "StaticBufferPublisher" EXECUTABLE "staticbufferpublisher")
install(TARGETS 
  StaticBufferPublisher
  AddressPrinter
  DESTINATION lib
)
install(DIRECTORY launch
  DESTINATION share/${PROJECT_NAME}
)
ament_package()


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source