Recently, I’ve been writing some C++ code that I would like to be ABI compliant. Because of that, I’ve been writing the code that uses a private class that holds all of the private data members. This results in code that looks something like the following:
// myclass.h
#include <memory>
class MyClass{
public:
MyClass();
~MyClass();
int foo() const;
void setFoo( int val );
private:
class priv;
std::unique_ptr<priv> m_priv;
};
// myclass.cpp
#include "myclass.h"
class MyClass::priv {
public:
priv() :
m_foo( 5 )
{}
int m_foo;
};
MyClass::MyClass(){
m_priv = std::make_unique<priv>();
}
MyClass::~MyClass(){
}
int MyClass::foo() const {
return m_priv->m_foo;
}
void MyClass::setFoo( int foo ){
m_priv->m_foo = foo;
}
However, after some further reading I realized that this creates a new problem with const methods: mainly that you can’t ensure const correctness! That is, if we change MyClass::foo to be the following, it’s perfectly legal:
int MyClass::foo() const {
m_priv->m_foo += 10;
return m_priv->m_foo;
}
Obviously, this particular design results in code that may look const-correct, but actually is not. You can get around that with having an actual pointer to the implementation and not just the private data members, but this seems like a lot of overhead and is really just a lot of boilerplate in my mind.
This leads me to some thoughts on what would make this nicer. The main problem seems to be this facet of C++:
Size and Layout: The calling code must know the size and layout of the class, including private data members.
https://herbsutter.com/gotw/_100/
Since the calling code needs to know the size and layout, any change to this causes ABI breakage, which is certainly not ideal, thus needing the pimpl pattern. What would be nice is if we didn’t have to do this. Thus, I bring my very simple 5-minute solution that hasn’t been thought out fully.
Add a new keyword! Called ‘privdata’.
Example usage:
// myclass.h
#include <memory>
class MyClass{
public:
MyClass();
~MyClass();
int foo() const;
void setFoo( int val );
private:
privdata int m_foo;
};
// myclass.cpp
#include "myclass.h"
MyClass::MyClass() : m_foo( 5 ){
}
MyClass::~MyClass(){
}
int MyClass::foo() const {
return m_foo;
}
void MyClass::setFoo( int foo ){
m_foo = foo;
}
The idea behind this is that just by declaring a private variable with ‘privdata’ effectively turns the code into what I posted before with the unique_ptr, so that no matter how many private variables you add(with the ‘privdata’ keyword) the class will stay the same size.
Advantages:
- One keyword to add
- Very easily make a class ABI compatible.
- No need to worry about a layer of indirection – the compiler takes care of this for you.
- Most tools(e.g. IDEs) will still work correctly. One problem with how I’ve been implementing is that Qt Creator doesn’t have nice auto completion for m_priv->…. That’s not the end of the world, but it would be nice!
- The compiler could still check for const correctness and only allow access in const methods and editing in non-const methods.
Disadvantages:
- I’ve spent longer writing this post than thinking about this, so I’m certainly missing something.
- Compilation speedups may not be possible at this point(assuming that you’re using make), since changing something in the header would cause all dependent files to be rebuilt. Maybe we need a new version of make that can parse code to know when things change and only rebuild when something important changes?
- This requires a lot of compiler changes. New standards require compiler changes anyway, but this probably can’t be implemented without compiler changes.
I did also start looking at some more recent posts from Herb Sutter, and I found this interesting post on making a clonable class. This makes me wonder if this would be possible to do using just standard C++ and some sort of reflection library…
If you want to learn more about the pimpl patter, check out the cppreference page, which also leads to Herb Sutter’s GotW page on pimpl. Herb Sutter’s page was useful for me in terms of learning about the alternatives and how they work.
ADDENDUM NEXT DAY: It turns out, this is possible using some experimental features(std::propagate_const). See this post on Stack Overflow for more information, or on cppreference!
Leave a Reply