Skip to content

How to Fix C++ PImpl Idiom Compilation and ABI Errors

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about How to Fix C++ PImpl Idiom Compilation and ABI Errors. We cover key concepts, practical examples, and best practices.

C++ PImpl (Pointer to Implementation) idiom errors like invalid application of 'sizeof' to incomplete type occur when the implementation class is not fully defined at the point where the unique_ptr destructor or copy operations are instantiated.

Quick Fix

Wrong

// widget.h
struct WidgetImpl;  // forward declaration

struct Widget {
    Widget();
    ~Widget();  // declared in header
    std::unique_ptr<WidgetImpl> impl_;
};

// widget.cpp
struct WidgetImpl {
    std::string name_;
    int value_;
};

Widget::Widget() : impl_(std::make_unique<WidgetImpl>()) {}
// Missing Widget::~Widget definition!

Build error: invalid application of 'sizeof' to incomplete type 'WidgetImpl' in the default unique_ptr deleter.

// widget.h
#include <memory>
struct WidgetImpl;

struct Widget {
    Widget();
    ~Widget();  // declared, not defined here
    Widget(Widget&&) noexcept;
    Widget& operator=(Widget&&) noexcept;
private:
    std::unique_ptr<WidgetImpl> impl_;
};

// widget.cpp
#include "widget.h"
#include <string>

struct WidgetImpl {
    std::string name_;
    int value_ = 0;
};

Widget::Widget() : impl_(std::make_unique<WidgetImpl>()) {}
Widget::~Widget() = default;
Widget::Widget(Widget&&) noexcept = default;
Widget& Widget::operator=(Widget&&) noexcept = default;

Fix for copy operations

// widget.h
Widget(const Widget& other);
Widget& operator=(const Widget& other);

// widget.cpp
Widget::Widget(const Widget& other)
    : impl_(std::make_unique<WidgetImpl>(*other.impl_)) {}
Widget& Widget::operator=(const Widget& other) {
    *impl_ = *other.impl_;
    return *this;
}

Prevention

  • Declare destructor in header but define it (even = default) in the .cpp file.
  • Declare move operations in header, define in .cpp.
  • Declare copy operations in header, define in .cpp (PImpl requires deep copy).
  • Keep the unique_ptr for the impl pointer.
  • Use std::make_unique in the constructor definition.

DodaTech Tools

Doda Browser's C++ PImpl validator checks for complete type requirements and proper ABI boundary separation. DodaZIP archives ABI compatibility reports. Durga Antivirus Pro detects ABI breaks from PImpl violations.

Common Mistakes with pimpl idiom

  1. Using head and tail instead of pattern matching, causing runtime errors on empty lists
  2. Forgetting that lazy evaluation defers computation until the value is forced, causing space leaks with unevaluated thunks
  3. Using return to exit a function early instead of wrapping a pure value in the monad

These mistakes appear frequently in real-world CPP code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

Why does PImpl need the destructor defined in the .cpp file?

The unique_ptr<WidgetImpl> destructor requires WidgetImpl to be a complete type. Defining the destructor in the .cpp file, where WidgetImpl is fully defined, allows the unique_ptr deleter to compile.

What are the benefits of PImpl?

Faster compilation (header changes only recompile the implementation file), ABI stability (private members can change without recompiling clients), and implementation hiding (users see only the public API).

Does PImpl affect performance?

Yes, PImpl adds a pointer indirection for every member access, heap allocation overhead, and prevents inlining of private member functions. Use PImpl for library boundaries, not for performance-critical internal types.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro