Pimpl my class: autogenerate PImpl boilerplate

Pimpl my class: autogenerate PImpl boilerplate#

PImpl is a common C++ programming technique that removes implementation details of a class from its object representation by placing them in a separate class, accessed through an opaque pointer.

Its advantages are:

  • Real code encapsulation (as opposed hiding details in private members, which remain clearly visible in the header file)

  • ABI stability is ensured (since the exposed class ABI consists of only one pointer)

  • Improve compilation time (since only the implemenation .cpp file will include the required dependencies)

Its drawbacks are:

  • An additional indirection for method calls

  • Writing a PImpl lead to a lot of boilerplate code, which is often a tedious to write

srcmlcpp_tools.pimpl_my_class enables you to automate the generation of the boiler code: given the source code of a PImpl class implementation it will generate two outputs:

  • a header file with the API that is exposed

  • some glue code that should be added to the C++ file

A simple example#

Let’s take an example.

Suppose we have this PImpl code somewhere in a C++ file of a class MyClassPImpl, and that we want to:

  • publish MyClassPImpl as MyClass in the header file,

  • publish all public methods,

  • publish all the documentation present in the PImpl class

First, let’s show the original code, that defines MyClassPImpl:

from srcmlcpp_tools import pimpl_my_class
from litgen.demo import litgen_demo

cpp_code = pimpl_my_class.demo_cpp_no_marker()
litgen_demo.show_cpp_code(cpp_code, "MyClassPImpl cpp file")
#include "my_class.h"

#include <string>
#include <future>

// Some doc about the class, that you want to see in the header file
class MyClassPImpl
{
public:
    //
    // Some doc you also want to see in the header file, since it is in a public block
    //

    // Construct an Instance
    MyClassPImpl(const std::string& someVar)
    {
        // Some code you provide in the C++ file, but do not want to see in the header file
    }

    // Some method
    bool SomeMethod() { /* ... */ }

    // Some public static method
    static bool SomeStaticMethod() { /* ... */ }

    // The destructor should not be published as is, but should use the unique_ptr default destructor
    ~MyClassPImpl() { /* ... */ }

private:
    //
    // Some private doc, which should not be published
    //

    bool SomePrivateMethod() { /* ... */ }

    std::string mSomePrivateMember;
    std::future<void> mAnoterPrivateMember;
};

We can generate the PImpl boilerplate code by calling pimpl_my_code:

pimpl_result = pimpl_my_class.pimpl_my_code(cpp_code)

if pimpl_result is not None:
    litgen_demo.show_cpp_code(pimpl_result.header_code, "Header Code")
    litgen_demo.show_cpp_code(pimpl_result.glue_code, "Glue Code (add this at the bottom of the C++ file)")
class MyClassPImpl;

// Some doc about the class, that you want to see in the header file
class MyClass
{
  public:
    //
    //  Some doc you also want to see in the header file, since it is in a public block
    //

    // Construct an Instance
    MyClass(const std::string & someVar);

    // Some method
    bool SomeMethod();

    // Some public static method
    static bool SomeStaticMethod();


    ~MyClass();
  private:
    std::unique_ptr<MyClassPImpl> mPImpl;
};
MyClass::MyClass(const std::string & someVar)
    : mPImpl(std::make_unique<MyClassPImpl>(someVar)) { }
bool MyClass::SomeMethod() {
    return mPImpl->SomeMethod(); }
bool MyClass::SomeStaticMethod() {
    return MyClassPImpl::SomeStaticMethod(); }
MyClass::~MyClass() = default;

Running PImplMyCode on a whole codebase#

By calling pimpl_my_class.pimpl_my_file it is possible to automatically add the generated code to a C++ and a header file.

These file should include markers (<pimpl_glue_code>, </pimpl_glue_code>, <pimpl_header_code>, and </pimpl_header_code>), to mark where the code should be added.

In order to keep this documentation short, we will automatically generate two example files (a C++ and a header file). Let’s show their content:

from srcmlcpp_tools import pimpl_my_class

cpp_file, header_file = pimpl_my_class.create_pimpl_example_files()

litgen_demo.show_cpp_file(cpp_file)
litgen_demo.show_cpp_file(header_file)
#include "my_class.h"

struct MyStructPImpl
{
    MyStructPImpl() { /* ...*/ }
    bool SomeMethod() { /* ... */ }
private:
    bool SomePrivateMethod() { /* ... */ }
    int mSomePrivateMember;
};

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  AUTOGENERATED CODE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// <pimpl_glue_code>  // Autogenerated code below! Do not edit!
// </pimpl_glue_code> // Autogenerated code end
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  AUTOGENERATED CODE END !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#pragma once
#include <memory>

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  AUTOGENERATED CODE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// <pimpl_header_code>  // Autogenerated code below! Do not edit!
// </pimpl_header_code> // Autogenerated code end
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  AUTOGENERATED CODE END !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

By running pimpl_my_file, these files will be modified, and the generated code will be inserted between the markers.

Below, we run pimpl_my_file and show the content of the files after it was run.

from srcmlcpp_tools.pimpl_my_class import pimpl_my_file, PimplOptions

pimpl_options = PimplOptions()
pimpl_options.pimpl_suffixes = ["PImpl"]

pimpl_my_file(cpp_file, header_file)

litgen_demo.show_cpp_file(cpp_file)
litgen_demo.show_cpp_file(header_file)
#include "my_class.h"

struct MyStructPImpl
{
    MyStructPImpl() { /* ...*/ }
    bool SomeMethod() { /* ... */ }
private:
    bool SomePrivateMethod() { /* ... */ }
    int mSomePrivateMember;
};

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  AUTOGENERATED CODE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// <pimpl_glue_code>  // Autogenerated code below! Do not edit!
MyStruct::MyStruct()
    : mPImpl(std::make_unique<MyStructPImpl>()) { }
bool MyStruct::SomeMethod() {
    return mPImpl->SomeMethod(); }
MyStruct::~MyStruct() = default;

// </pimpl_glue_code> // Autogenerated code end
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  AUTOGENERATED CODE END !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#pragma once
#include <memory>

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  AUTOGENERATED CODE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// <pimpl_header_code>  // Autogenerated code below! Do not edit!
struct MyStructPImpl;


struct MyStruct
{

    MyStruct();
    bool SomeMethod();
    ~MyStruct();
  private:
    std::unique_ptr<MyStructPImpl> mPImpl;
};
// </pimpl_header_code> // Autogenerated code end
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  AUTOGENERATED CODE END !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!