This article is a continuation of Pimpl idiom prior to C++11.
So as mentioned earlier, we should NOT use raw pointers from C++11 onwards unless until it is absolutely unavoidable. Let's go ahead and replace the raw pointer in Car.h by a std::unique_ptr
The code above compiles fine. But in our main file, if we do something as simple as this, the compiler throws various compiler errors.
The errors are -
This is because the
Car::CarImpl is incomplete at the point where compiler generated functions are usually created i..e Car.h file.
Hence we have to move the auto generated functions to Car.cpp file, where Car::CarImpl is a complete type.
This includes the destructor and move functions. We define move instructions since objects with Pimpl idioms usually support move operations. The compiler generated versions of these functions should usually suffice. But in order to generate it at the right place ( where the Car::CarImpl is a complete type), we have to do the following -
The above program compiles fine. But since we have defined move functions explicitly, we have to define copy instructions as well. Adding the copy operations, we get -
Most Pimpl idioms implementations use std::unique_ptr. But if we were to use std::shared_ptr, the compiler would have generated all the inbuilt functions without any errors. This is because deleter is not part of the type in shared_ptr. Thus std::shared_ptr uses more runtime data structures, and resulting in slightly slower code.
To conclude this article explains the correct way of implementing Pimpl idiom in Modern C++.
So as mentioned earlier, we should NOT use raw pointers from C++11 onwards unless until it is absolutely unavoidable. Let's go ahead and replace the raw pointer in Car.h by a std::unique_ptr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Car.h | |
#pragma once | |
#include <memory> | |
#include <string> | |
class Car | |
{ | |
public: | |
Car(); | |
void move(int src, int dest) const; | |
void addFeature(std::string, std::string); | |
private: | |
struct CarImpl; | |
std::unique_ptr<CarImpl> impl; | |
}; | |
// Car.cpp | |
#include "Car.h" | |
#include <iostream> | |
#include <map> | |
struct Car::CarImpl | |
{ | |
int numOfWheels; | |
std::string makeOfCar; | |
std::map<std::string, std::string> features; | |
}; | |
Car::Car() : impl(std::make_unique<Car::CarImpl>()) | |
{ | |
} | |
void Car::move(int src, int dest) const | |
{ | |
std::cout << "Moved from " << src << " to " << dest << '\n'; | |
} | |
void Car::addFeature(std::string key, std::string val) | |
{ | |
this->impl->features[key] = val; | |
} |
The code above compiles fine. But in our main file, if we do something as simple as this, the compiler throws various compiler errors.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "Car.h" | |
int main() | |
{ | |
Car c; | |
} |
The errors are -
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.12.25827\include\memory(2125): error C2027: use of undefined type 'Car::CarImpl' | |
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.12.25827\include\memory(2125): error C2338: can't delete an incomplete type | |
Hence we have to move the auto generated functions to Car.cpp file, where Car::CarImpl is a complete type.
This includes the destructor and move functions. We define move instructions since objects with Pimpl idioms usually support move operations. The compiler generated versions of these functions should usually suffice. But in order to generate it at the right place ( where the Car::CarImpl is a complete type), we have to do the following -
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Car.h | |
#pragma once | |
#include <memory> | |
#include <string> | |
class Car | |
{ | |
public: | |
Car(); | |
void move(int src, int dest) const; | |
void addFeature(std::string, std::string); | |
// Move functions declared | |
Car& operator=(Car&&); | |
Car(Car&&); | |
// Destructor declared | |
~Car(); | |
private: | |
struct CarImpl; | |
std::unique_ptr<CarImpl> impl; | |
}; | |
// Car.cpp | |
#include "Car.h" | |
#include <iostream> | |
#include <map> | |
struct Car::CarImpl | |
{ | |
int numOfWheels; | |
std::string makeOfCar; | |
std::map<std::string, std::string> features; | |
}; | |
Car::Car() : impl(std::make_unique<Car::CarImpl>()) | |
{ | |
} | |
void Car::move(int src, int dest) const | |
{ | |
std::cout << "Moved from " << src << " to " << dest << '\n'; | |
} | |
void Car::addFeature(std::string key, std::string val) | |
{ | |
this->impl->features[key] = val; | |
} | |
// Move operation defined here | |
Car& Car::operator=(Car&& anotherCar) = default; | |
Car::Car(Car&& anotherCar) = default; | |
// Destructor defined here | |
Car::~Car() = default; | |
// mainFile.cpp | |
#include "Car.h" | |
int main() | |
{ | |
Car c; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Car.h | |
#include <memory> | |
#include <string> | |
class Car | |
{ | |
public: | |
Car(); | |
void move(int src, int dest) const; | |
void addFeature(std::string, std::string); | |
// Move functions declared | |
Car& operator=(Car&&); | |
Car(Car&&); | |
// Copy functions declared | |
Car& operator=(const Car&); | |
Car(const Car&); | |
// Destructor declared | |
~Car(); | |
private: | |
struct CarImpl; | |
std::unique_ptr<CarImpl> impl; | |
}; | |
// Car.cpp | |
#include "Car.h" | |
#include <iostream> | |
#include <map> | |
struct Car::CarImpl | |
{ | |
int numOfWheels; | |
std::string makeOfCar; | |
std::map<std::string, std::string> features; | |
}; | |
// constructor | |
Car::Car() : impl(std::make_unique<Car::CarImpl>()) | |
{ | |
} | |
void Car::move(int src, int dest) const | |
{ | |
std::cout << "Moved from " << src << " to " << dest << '\n'; | |
} | |
void Car::addFeature(std::string key, std::string val) | |
{ | |
this->impl->features[key] = val; | |
} | |
// Move functions defined here | |
Car& Car::operator=(Car&& anotherCar) = default; | |
Car::Car(Car&& anotherCar) = default; | |
// Copy functions defined here | |
Car& Car::operator=(const Car& anotherCar) | |
{ | |
*impl = *anotherCar.impl; | |
return *this; | |
} | |
Car::Car(const Car& anotherCar) : impl(std::make_unique<Car::CarImpl>(*anotherCar.impl)) | |
{} | |
// Destructor defined here | |
Car::~Car() = default; |
To conclude this article explains the correct way of implementing Pimpl idiom in Modern C++.
Modern (Effective) C++ - Pimpl Idiom from C++11 onwards
Reviewed by zeroingTheDot
on
February 08, 2018
Rating:

Sands Casino NJ - Slots, Roulette, and Much More
ReplyDeleteEntertainment 바카라사이트 · Casino Games · About Us · Dining 제왕 카지노 · Entertainment · 샌즈카지노 Lodging