|
Posts Tagged ‘Design Patterns’
Jul
28
2010
Developing Well-Designed Packages for Robot Operating System (ROS), Part VPart V: Developing and Testing the ROS Message Endpoint [Author's note, July 28, 2010: Introduced Boost::shared_ptr to manage reference to message endpoint so as to enable postponed construction, as will be required in Part VI.] While this series (Part I, II, III, IV) has been specifically written to address writing well-designed packages for ROS, we’ve actually seen very little of ROS itself thus far. In fact, outside of the use of roscreate to generate the package basics and sensor_msgs::LaserScan for communicating laser scan data from the reader up to the application services layer, there’s been no indication that this application was actually intended to work with ROS now or ever. Ironically, this is exactly what we’d expect to see in a well designed ROS package. Each layer that we’ve developed – as initially outlined in Part I – is logically separated from each other’s context of responsibility. To illustrate, the upper layers do not directly depend on “service” layers, such as message endpoints. Instead, the lower layers depend on abstract service interfaces declared in the upper layers. This dependency inversion was enabled in Part IV with the creation of ILaserScanEndpoint, a separated interface. If all of this dependency inversion and separated interface mumbo-jumbo has your head spinning at all, take some time to delve deeper into this subject in Dependency Injection 101. While the actual message endpoint interface was created, only a test double was developed for testing the application service layer’s functionality. Accordingly, in this post, the concrete message endpoint “service,” which implements its separated interface, will be developed and tested. That’s right…we’ll finally actually talk to ROS! You can skip to the chase and download the source for this post. Before digging into the code, it’s important to take a moment to better understand the purpose and usefulness of the message endpoint. The message endpoint encapsulates communications to the messaging middleware similarly to how a data repository encapsulates communications to a database. By encapsulating such communications, the rest of the application (ROS package, in our case) may remain blissfully oblivious to details such as how to publish messages to a topic or translate between messages and domain layer objects. This separation of concerns helps to keep the application cleanly decoupled from the messaging middleware. Another benefit of this approach is enabling the development and testing of nearly the entirety of the application/package before “wiring” it up to the messaging middleware itself. This typically results in more reusable and readable code. If you haven’t already, I would encourage you to read the article Message-Based Systems for Maintainable, Asynchronous Development for a more complete discussion on message endpoints. Onward! Target Class Diagram The following diagram shows what the package will look like after completing the steps in this post…it’s beginning to look oddly familiar to the package diagram discussed in Part I of this series, isn’t it? If you’ve been following along, most of the elements have already been completed; only the concrete LaserScanEndpoint and LaserScanEndpointTests will need to be introduced along with a slight modification to the TestRunner.
1. Setup the Package Skeleton, Domain Layer and Application Services Layer If not done already, follow the steps in Part II, Part III, and Part IV to create the package and develop/test the domain and application service layers. (Or just download the code from Part IV as a starting point to save some time.) 2. Create the message endpoint header class. Create src/message_endpoints/LaserScanEndpoint.hpp containing the following code: // LaserScanEndpoint.hpp #ifndef GUARD_LaserScanEndpoint #define GUARD_LaserScanEndpoint #include <ros/ros.h> #include "sensor_msgs/LaserScan.h" #include "ILaserScanEndpoint.hpp" namespace ladar_reporter_message_endpoints { class LaserScanEndpoint : public ladar_reporter_core::ILaserScanEndpoint { public: LaserScanEndpoint(); void publish(const sensor_msgs::LaserScan& laserScan) const; private: // Create handle to node ros::NodeHandle _ladarReporterNode; ros::Publisher _laserReportPublisher; }; } #endif /* GUARD_LaserScanEndpoint */ The message endpoint header simply implements ILaserScanEndpoint and sets up handlers for holding the ROS NodeHandle and Publisher. The more interesting bits are found in the implementation details… 3. Create the message endpoint implementation class. Create src/message_endpoints/LaserScanEndpoint.cpp containing the following code: // LaserScanEndpoint.cpp #include <ros/ros.h> #include "sensor_msgs/LaserScan.h" #include "LaserScanEndpoint.hpp" namespace ladar_reporter_message_endpoints { LaserScanEndpoint::LaserScanEndpoint() // Setup topic for publishing laser scans to : _laserReportPublisher( _ladarReporterNode.advertise<sensor_msgs::LaserScan>("laser_report", 100)) { } void LaserScanEndpoint::publish(const sensor_msgs::LaserScan& laserScan) const { _laserReportPublisher.publish(laserScan); ros::spinOnce(); ROS_INFO("Published laser scan to laser_report topic with angle_min of: %f", laserScan.angle_min); }; } As you can see, there’s really not much to the actual publication process…which is what we were hoping for. The message endpoint should simply be a light way means to send and receive messages to/from the messaging middleware. This message endpoint does so as follows:
4. Configure CMake to Include the Header and Implementation With the header and implementation classes completed, we need to make a couple of minor modifications to CMake for their inclusion in the build.
5. Build the message endpoints Class Library In a terminal window, cd to /ladar_reporter and run Like with everything else thus far…it’s now time to test our new functionality. 6. Unit Test the LaserScanEndpoint Functionality While testing up to this point has been pretty straight-forward, we now need to incorporate ROS package initialization within the test itself.
The first five parts of this series conclude the primary elements of developing well-designed packages for Robot Operating System (ROS) using proven design patterns and proper separation of concerns. Obviously, this is not a trivially simple approach to developing ROS packages; indeed, it would be overkill for very simple packages. But as packages grow in size, scope, and complexity, techniques described in this series should help to establish a maintainable, extensible package which doesn’t get too unruly as it evolves. In Part VI, the final part in this series, we’ll look at adding a simple UI layer, using wxWidgets, to interact with the package functionality. Enjoy! Download the source for this post. Jun
19
2010
Developing Well-Designed Packages for Robot Operating System (ROS), Part IVPart IV: Developing and Testing the Application Services [Author's note, July 28, 2010: Introduced Boost::shared_ptr to manage reference to message endpoint so as to enable postponed construction, as will be required in Part VI.] Ah, we’ve made it to the application services layer. After defining the architecture, setting up the package, and implementing the core layer which contains the domain logic of the application, we’re ready to take on developing the application services layer. In short, the service layer provides the external API to the underlying domain layer. Ideally, one should be able to develop the entirety of the “non-UI” portions of an application and have it exposed via the application services layer. In other words, one should be able to swap out the front end – say from a web front end to a Flash front end – without having the application services layer effected. If you’d like to go ahead and download the source for this article, click here. The application services layer is analogous to a task or coordination manager; i.e., it doesn’t know how to carry out the low level details of a particular action but it does know who is responsible for carrying out particular tasks. Accordingly, the application services layer of the package (or any application for that matter) is mostly made up of a number of publicly accessible methods which, in turn, pass responsibilities on to external service dependencies (e.g., a message endpoint) and the domain layer for execution. With that said, the services layer should still eschew direct dependencies on external services, such as communications with a messaging framework (e.g., ROS). Accordingly, in this post, we’ll materialize the application services layer of our package and give it two responsibilities:
But (there’s always a “but” isn’t there), while the application services layer should be responsible for passing messages received from the domain layer on to the messaging middleware, it should not have a direct dependency on that messaging middleware itself. This decoupling facilitates testing of the application services layer with test doubles, keeps a clean separation of concerns between task coordination and messaging, and provides greater flexibility with being able to modify/upgrade the messaging layer without affecting the application services layer. A moment needs to be taken to clarify the differences among application services, domain services, and “external resource” services.
Before we delve in, let’s briefly review what we plan to accomplish:
Enough with the chatter, let’s see some code! Target Class Diagram The following diagram shows what the package will look like after completing the steps in this post. While the individual elements will be discussed in more detail; the class diagram should serve as a good bird’s eye view of the current objectives. The elements with green check marks were completed in previous posts.
1. Setup the Package Skeleton and Domain Layer If not done already, follow the steps in Part II and Part III to create the package and develop/test the domain layer. 2. Create an interface for the message endpoint service which the application service layer will leverage to communicate with ROS. // ILaserScanEndpoint.hpp #ifndef GUARD_ILaserScanEndpoint #define GUARD_ILaserScanEndpoint #include "sensor_msgs/LaserScan.h" namespace ladar_reporter_core { class ILaserScanEndpoint { public: // Virtual destructor to pass pointer ownership without exposing base class [Meyers, 2005, Item 7] virtual ~ILaserScanEndpoint() {} virtual void publish(const sensor_msgs::LaserScan& laserScan) const = 0; }; } #endif /* GUARD_ILaserScanEndpoint */ There shouldn’t be anything too surprising in this interface. It simply exposes a method to publish a laser scan to the underlying messaging middleware. Why not put the interface in the application services layer, which intends to use it? A couple of good reasons come to mind: 1) since it’s a pure interface, having elements within core aware of it (or even directly dependent upon it) does not introduce any further coupling to the underlying external resource, the messaging middleware, and 2) in very simply packages, an application services layer might be overkill, so keeping the “external resource service interface” (there’s a mouthful) in the core layer facilitates either approach without having to move anything around if the selected approach changes during development. 3. Create the application service header class. Create src/application_services/LaserScanReportingService.hpp containing the following code: // LaserScanReportingService.hpp #ifndef GUARD_LaserScanReportingService #define GUARD_LaserScanReportingService #include <boost/shared_ptr.hpp> #include "ILaserScanEndpoint.hpp" namespace ladar_reporter_application_services { class LaserScanReportingService { public: explicit LaserScanReportingService(boost::shared_ptr<ladar_reporter_core::ILaserScanEndpoint> laserScanEndpoint); void beginReporting() const; void stopReporting() const; private: // Forward declare the implementation class class LaserScanReportingServiceImpl; boost::shared_ptr<LaserScanReportingServiceImpl> _pImpl; }; } #endif /* GUARD_LaserScanReportingService */ A few notes:
4. Create the application service implementation class. Create src/application_services/LaserScanReportingService.cpp containing the following code: // LaserScanReportingService.cpp #include "sensor_msgs/LaserScan.h" #include "ILaserScanListener.hpp" #include "LaserScanReader.hpp" #include "LaserScanReportingService.hpp" using namespace ladar_reporter_core; namespace ladar_reporter_application_services { // Private implementation of LaserScanReportingService class LaserScanReportingService::LaserScanReportingServiceImpl : public ladar_reporter_core::ILaserScanListener { public: explicit LaserScanReportingServiceImpl(boost::shared_ptr<ILaserScanEndpoint> laserScanEndpoint); void onLaserScanAvailableEvent(const sensor_msgs::LaserScan& laserScan) const; LaserScanReader laserScanReader; private: boost::shared_ptr<ILaserScanEndpoint> _laserScanEndpoint; }; LaserScanReportingService::LaserScanReportingService(boost::shared_ptr<ILaserScanEndpoint> laserScanEndpoint) : _pImpl(new LaserScanReportingServiceImpl(laserScanEndpoint)) { // Wire up the reader to the handler of laser scan reports _pImpl->laserScanReader.attach(*_pImpl); } LaserScanReportingService::LaserScanReportingServiceImpl::LaserScanReportingServiceImpl(boost::shared_ptr<ILaserScanEndpoint> laserScanEndpoint) : _laserScanEndpoint(laserScanEndpoint) { } void LaserScanReportingService::beginReporting() const { _pImpl->laserScanReader.beginReading(); } void LaserScanReportingService::stopReporting() const { _pImpl->laserScanReader.stopReading(); } void LaserScanReportingService::LaserScanReportingServiceImpl::onLaserScanAvailableEvent(const sensor_msgs::LaserScan& laserScan) const { // Send laserScan to the message end point _laserScanEndpoint->publish(laserScan); }; } A few notes:
As mentioned previously, the above implementation could be done without a private implementation design pattern, but this serves to illustrate how such a pattern may be leveraged when warranted. 5. Configure CMake to Include the Header and Implementation With the header and implementation classes completed, we need to make a couple of minor modifications to CMake for their inclusion in the build.
6. Build the application services Class Library In a terminal window, cd to /ladar_reporter and run Like before, we’re not done yet…it’s now time to test our new functionality. 7. Unit Test the LaserScanReportingService Functionality When we go to test the functionality of the laser scan reporting application service, it will likely be quickly noticed that there’s a missing dependency which will be needed to test the functionality of this class: a concrete implementation of ILaserScanEndpoint.hpp. The job of the message endpoint class will be to take laser scans and publish them to the appropriate topic on ROS. But we’re just not there yet…what we’d really like to do is to be able to test the functionality of the application service layer – LaserScanReportingService.cpp to be specific – without necessitating the presence of the message endpoint and ROS itself. While we’ll have to cross that bridge eventually (in Part V to be exact), we’re not currently interested in doing integration tests. Instead, we’re only interested in testing the behavior of the application service regardless of its integration with ROS. Accordingly, a “stub” object will be employed to stand in for an actual message endpoint. In this case, the stub object is nothing more than a concrete implementation of ILaserScanEndpoint.hpp; but instead of publishing the laser scan to ROS, it’ll do something testable, such as keep a tally, or possibly a temporary queue, of laser scans “published” which can then be verified with testing asserts. If you’re new to unit testing, you’ll want to read about test doubles, Martin Fowler’s Mocks Aren’t Stubs, and XUnit Test Patterns for a more comprehensive treatment of the subject. Onward with testing…
While running the tests, you should see a few messages published to the message endpoint stub. This demonstrates that all of the interactions among our core and application service layers are occurring exactly as expected. Now for the fun part… In Part V, we’ll finally take a look at using all of these pieces together in order to publish messages to a ROS topic via a message endpoint. Enjoy! May
20
2010
Developing Well-Designed Packages for Robot Operating System (ROS), Part IIIPart III: Developing and Testing the Domain Layer [Author's note, July 28, 2010: Fixed minor bug in LaserScanReader.cpp wherein it couldn't be restarted after being stopped; had to reset _stopRequested to false.] In Part II of this series, we created the humble beginnings of the package and added folders to accommodate all of the layers of our end product, a well-designed ROS package that reports (fake) laser scan reports. In this post, the domain layer of the package will be fleshed out along with unit tests to verify the model and functionality, accordingly. The entire focus will be on implementing just one of the requirements initially described in Part I: The package will read laser reports coming from a laser range-finder. If you’d like to download the resulting source for this article, click here. That certainly sounds easy enough. Disregarding the previous discussions concerning architecture, the gut reaction might be to start adding code to main(), simply taking the results from the range-finder, turning them directly into a ROS message, and publishing the messages to the appropriate ROS topic. This myopic “get ‘er done” approach quickly gets out of hand as main() turns into a tangled mess of code managing a variety of responsibilities. Object oriented principles aside, having all of these separate concerns mashed into main turns the little package into a maintenance nightmare with little ability to reuse code. As mentioned, the first concern that we’ll want to tackle is the ability to read laser range-finder reports. We’ll tackle this requirement by encapsulating the range-finder integration code within a class called LaserScanReader.cpp. By doing so, all of the communications to the range-finder are properly encapsulated within one or more classes, making the integration code easier to reuse and maintain. To keep our focus on the overall architecture, and to avoid the need to have a physical range-finder handy, we’ll simulate range-finder communications within LaserScanReader.cpp. Certainly an added benefit of this approach, if we were doing this for a real-world package, is that one group could work on the “rest” of the package while another group works on the actually range-finder communications; so when ready, LaserScanReader.cpp could be switched out with the “real” range-finder integration code. Prerequisites:
Before proceeding, recall that, as described in Part I, the domain layer of the package should not have knowledge concerning how to communicate with the messaging middleware directly (e.g., ROS). This implies that the domain layer should have no direct dependencies on the messaging middleware. This allows the domain layer to be more easily reused with another messaging middleware solution. Additionally, keeping this clean separation of concerns facilitates the testing of the domain layer independently from its interactions with the messaging middleware. Accordingly, the simple domain layer developed in this post will adhere to this guidance along with full testing for verification of capabilities as well. Our LaserScanReader class will expose two methods, beginReading() and stopReading(), along with an observer hook to provide a call-back to be invoked whenever a new reading is available. For now, we won’t worry about what exactly will be called back in the completed package, as that’ll be a concern of the application services layer; but we’ll need to prepare for it by including an interface for the laser scan observer. Target Class Diagram The following diagram shows what the package will look like after completing the steps in this post. While the individual elements will be discussed in more detail; the class diagram should serve as a good bird’s eye view of the current objectives.
1. Setup the Package Skeleton If not done already, follow the steps in Part II to create the beginnings of the package. 2. Create the ILaserScanListener Observer Header Whenever a laser scan is read, it’ll need to be given to whomever is interested in it. It should not be the responsibility of the laser scan reader to predict who will want the laser scans. Accordingly, an observer interface should be introduced which the laser scan reader will communicate through to raise laser scan events to an arbitrary number of listeners. Add a new interface header file to /ladar_reporter/src/core called ILaserScanListener.hpp containing the following code: // ILaserScanListener.hpp #ifndef GUARD_ILaserScanListener #define GUARD_ILaserScanListener #include "sensor_msgs/LaserScan.h" namespace ladar_reporter_core { class ILaserScanListener { public: // Virtual destructor to pass pointer ownership without exposing base class [Meyers, 2005, Item 7] virtual ~ILaserScanListener() {} virtual void onLaserScanAvailableEvent(const sensor_msgs::LaserScan& laserScan) const = 0; }; } #endif /* GUARD_ILaserScanListener */ As you can see, this C++ interface (or as close as you can get to an interface in C++) simply exposes a single function to handle laser scan events. 3. Create the LaserScanReader Header Add a new class header file to /ladar_reporter/src/core called LaserScanReader.hpp containing the following code, which we’ll discuss in detail below. // LaserScanReader.hpp #ifndef GUARD_LaserScanReader #define GUARD_LaserScanReader #include <pthread.h> #include <vector> #include "sensor_msgs/LaserScan.h" #include "ILaserScanListener.hpp" namespace ladar_reporter_core { class LaserScanReader { public: LaserScanReader(); void beginReading(); void stopReading(); // Provides a call-back mechanism for objects interested in receiving scans void attach(ILaserScanListener& laserScanListener); private: void readLaserScans(); void notifyLaserScanListeners(const sensor_msgs::LaserScan& laserScan); std::vector<ILaserScanListener*> _laserScanListeners; // Basic threading support as suggested by Jeremy Friesner at // http://stackoverflow.com/questions/1151582/pthread-function-from-a-class volatile bool _stopRequested; volatile bool _running; pthread_t _thread; static void * readLaserScansFunction(void * This) { ((LaserScanReader *)This)->readLaserScans(); return 0; } }; } #endif /* GUARD_LaserScanReader */ Let’s now review the more interesting parts of the header class:
4. Create the LaserScanReader Class Implementation Add a new class file to /ladar_reporter/src/core called LaserScanReader.cpp containing the following code, which we’ll discuss in detail below. // LaserScanReader.cpp #include "LaserScanReader.hpp" namespace ladar_reporter_core { LaserScanReader::LaserScanReader() : _stopRequested(false), _running(false) { _laserScanListeners.reserve(1); } void LaserScanReader::attach(ILaserScanListener& laserScanListener) { _laserScanListeners.push_back(&laserScanListener); } void LaserScanReader::beginReading() { if (! _running) { _running = true; _stopRequested = false; // Spawn async thread for reading laser scans pthread_create(&_thread, 0, readLaserScansFunction, this); } } void LaserScanReader::stopReading() { if (_running) { _running = false; _stopRequested = true; // Wait to return until _thread has completed pthread_join(_thread, 0); } } void LaserScanReader::readLaserScans() { int i = 0; while (! _stopRequested) { sensor_msgs::LaserScan laserScan; // Just set the angle_min to include some data laserScan.angle_min = ++i; notifyLaserScanListeners(laserScan); sleep(1); } } void LaserScanReader::notifyLaserScanListeners(const sensor_msgs::LaserScan& laserScan) { for (int i= 0; i < _laserScanListeners.size(); i++) { _laserScanListeners[i]->onLaserScanAvailableEvent(laserScan); } } } Let’s now review the more interesting parts of the class implementation:
5. Add a ROS sensor_msgs Dependency to manifest.xml Since the code above refers to sensor_msgs::LaserScan, a dependency needs to be added for the package to use this class, accordingly:
6. Configure CMake to Include both the Header and Implementation With the header and implementation classes completed, we need to make a couple of minor modifications to CMake for their inclusion in the build. First, open /ladar_reporter/CMakeLists.txt and make the following modifications:
In doing so, the LaserScanReader header has been included for consumption by other classes and application layers. This inclusion has been done in the root CMakeLists.txt as this will make the headers available to the unit tests as well. For more complex packages, this approach of including the headers in the root CMakeLists.txt, to make them globally accessible, may become a bit messy; it may be more appropriate to put the header inclusions in only the CMakeLists.txt which actually require the respective headers if the package is larger. At this point, a new CMake file is needed under /ladar_report/src:
You’ll quickly notice that this CMake file has merely passed the buck of defining class libraries further down the chain. Accordingly, a CMakeLists.txt will be setup for each of the package layers including application_services, core, message_endpoints, and ui. All of these layers will be compiled into separate class libraries and, finally, an executable. Arguably, all of these layers could be combined into a single executable with a single CMakeLists.txt file. But keeping them in separate class libraries keeps a clean separation of concerns in their respective responsibilities and makes each aspect of our package more easily testable in isolation from the other layers. Next, in order to create the core class library, a new CMake file is needed under /ladar_reporter/src/core:
We’re now ready to compile the class library for the “core” layer of the package… 7. Build the core Class Library In a terminal window, cd to /ladar_reporter and run Woohoo! Done, right? Well, not yet…time to test our new functionality. 8. Unit Test the LadarScanReader Functionality So far, you’ve had to simply assume that a successful build means everything is working as expected. Obviously, when developing a ROS package, we’ll want a bit more reassurance than a successful build to be confident that the developed capabilities are working as expected. Accordingly, unit tests should be developed to test the functionality; in the case at hand, a unit test will be developed to initialize, begin and stop the laser reading cycle to ensure that it is raising laser scan events as designed. The standard ROS testing tool is gtest; this is a great choice as gtest is very easy to setup and provides informative output during unit test execution. To setup and run unit tests for the package, only two elements are needed: a “test runner” to act as the unit tests’ main and to execute all of the package unit tests, and the unit tests themselves which may be spread out among a variety of folders and classes. When unit testing, I typically write one unit testing class (a test fixture) per class being tested. Furthermore, I include one unit test for each public function or behavior of the class being tested. As for a couple of other unit testing best practices, be sure to keep the unit tests independent from each other – they should be able to be run in isolation without being dependent on the running of other unit tests. Additionally, each unit test is organized as three stages of the test: “establish context” wherein the testing context is setup, “act” wherein the desired behavior is invoked, and “assert” wherein the results of the behavior are verified. Finally, no testing code should be added to the “production” source code itself; all tests should be maintained in a separate executable to keep a clean separation of concerns between application code and tests. We’ll see an example of this as we proceed.
When the test runs, you should see a few laser scan angle_min values get printed to the terminal along with the final report that the test successfully passed. Now that we’ve done all of the above to complete the core domain layer of the package, let’s review what have was accomplished:
In Part IV, we’ll be setting our sights on developing and testing the application services layer of the application with a test double standing in for the ROS messaging system. Enjoy! Apr
29
2010
Passing Containing Parent to pImpl Idiom Implementation ClassThe pImpl Idiom is a useful technique for hiding implementation details of C++ classes away from being exposed in the header file. A couple of the primary benefits include decoupling of the implementation from the interface of the object (as declared in the header) and reduced compilation time if only the pImpl class is modified. As a rule of thumb, when using pImpl Idiom, there should be clean separation of any implementation details from the containing class. Spreading implementation details across both the containing class and the pImpl class muddies the separation of concerns and can quickly lead to maintenance issues, with unclear direction as to what should go into the parent vs. the pImpl class. Accordingly, public methods declared in the parent should simply act as pass-through methods to pImpl. I’ve seen arguments that any public method which does not require access to the internal state of pImpl may instead be defined on the parent and not require a pass-through; this sounds reasonable while still providing a gauge for cleanly deciding where implementation definitions should reside (i.e., if it’s a public method which doesn’t require access to pImpl state, define it on the parent). To that end, there may be scenarios wherein the pImpl class needs to invoke a method or property on the parent. Accordingly, this post shows how to pass a reference to the parent object to the pImpl object while avoiding a copy of the parent in the process. The first step is in defining the header class which will expose the public methods and properties along with a forward declaration of the pImpl class: // LaserScanReader.hpp #ifndef GUARD_LaserScanReader #define GUARD_LaserScanReader #include <boost/shared_ptr.hpp> namespace ladar_reporter_core { class LaserScanReader { public: ... private: // Forward declare the implementation class class LaserScanReaderImpl; boost::shared_ptr<LaserScanReaderImpl> _pImpl; }; } #endif /* GUARD_LaserScanReader */ Note that the _pImpl member is stored as a boost::shared_ptr so that _pImpl will be destructed automatically when it loses all references to it. Now that the parent has been declared, we can turn our attention towards the implementation class. // LaserScanReader.cpp #include "LaserScanReader.hpp" namespace ladar_reporter_core { // Private implementation class LaserScanReader::LaserScanReaderImpl { public: explicit LaserScanReaderImpl(const LaserScanReader& laserScanReader); private: // Provides access to methods/properties of parent; e.g., _laserScanReader.someProperty const LaserScanReader& _laserScanReader; }; LaserScanReader::LaserScanReader() // Passes a reference of LaserScanReader to LaserScanReaderImpl (not a copy) : _pImpl(new LaserScanReaderImpl(*this)) { } LaserScanReader::LaserScanReaderImpl::LaserScanReaderImpl(const LaserScanReader& laserScanReader) // Initialize the reference to the LaserScanReader (parent object of _pImpl) : _laserScanReader(laserScanReader) { } } Let’s now look at a few of the interesting points in more detail.
With that, the pImpl class now has access to the parent object’s properties and methods via its Billy McCafferty |
© 2011-2012 Codai, Inc. All Rights Reserved