Constructor and destructor in c++ when using the pimpl idiom - c++

I come from Java that has a different way in handling what's private and has to be hided regarding a class implementation and it also has a garbage collector which means there is no need for a destructor.
I learned the basics of how to implement a class in c++ but I need to better understand how to implement a class, in particular the constructor and destructor, when using the pimpl idiom.
hpp file:
class MyClass{
public:
MyClass();
MyClass(std::vector<int>& arr);
~MyClass();
private:
struct Impl;
Impl* pimpl;
};
cpp file:
#include "MyClass.hpp"
using namespace std;
struct MyClass::Impl{
vector<int> arr;
int var;
};
I wrote the sample code of a class I need to work on(which means I can't change the way the pimpl idiom is used) using the pimpl idiom and I'm looking for an answer to these questions:
How do I implement the constructor to create an empty instance of MyClass?
MyClass::MyClass(){}
How do I implement the constructor to create an instance of MyClass with these arguments?
MyClass::MyClass(vector<int>& arr,int var){}
How do I implement the destructor?
MyClass::~MyClass(){}
EDIT:
How I would do it:
MyClass::MyClass(): pimpl(new Impl) {}
MyClass::MyClass(vector<int>& arr,int var): pimpl(new Impl) {
pimpl->arr=arr;
pimpl->var=var;
}
MyClass::~MyClass(){
delete pimpl;
}

This is an example of a correct way to implement PIMPL idiom in modern C++:
foo.hpp
#pragma once
#include <memory>
class Foo {
public:
Foo();
~Foo();
Foo(Foo const &) = delete;
Foo &operator=(Foo const &) = delete;
Foo(Foo &&) noexcept;
Foo &operator=(Foo &&) noexcept;
void bar();
private:
class impl;
std::unique_ptr<impl> pimpl_;
};
foo.cpp:
#include "foo.hpp"
#include <iostream>
class Foo::impl {
public:
impl() = default;
~impl() = default;
impl(impl const &) = default;
impl &operator=(impl const &) = default;
impl(impl &&) noexcept = default;
impl &operator=(impl &&) noexcept = default;
void bar() { std::cout << "bar" << std::endl; }
};
Foo::Foo() : pimpl_(new impl{}) {}
Foo::~Foo() = default;
Foo::Foo(Foo &&) noexcept = default;
Foo &Foo::operator=(Foo &&) noexcept = default;
void Foo::bar() { pimpl_->bar(); }
main.cpp:
#include "foo.hpp"
int main(int argc, char const *argv[]) {
Foo foo;
foo.bar();
return 0;
}
There are a few words that are to be said:
use a smart pointer (unique or shared) to hold reference(s) to 'impl' object. It'll help you like in JAVA control number of references to an underlying object. In this example, as just Foo class is gone 'impl' is automatically destroyed, there is no need to manually watch the lifetime of the 'impl' object
'impl' MUST be always completely declared and defined in *.cpp file (or bunch of files) or internal *.hpp and/or *.cpp files so that a user of your front class (i.e., in this example 'Foo') that aggregates 'impl' is impossible to see any changes and content of your 'impl' class, that is why PIMPL idiom exists: to persist the interface of the front class for a user and all the changes mostly shall be done in the hidden 'impl' class
The destructor of the front class MUST be always defined in *.cpp file since the compiler must know how to destroy the 'impl' that is not seen thoroughly from the *.hpp file where a front class is declared
The special functions with rvalue-references (move-ctor and move-assignment operator) MUST also be defined in *.cpp file due to the same reason as in the previous clause

Related

What is the minimal class to extend for a no copy / no move type?

Let's say I want to create some classes to manage resources that shouldn't be copied nor moved, which would be the minimal class to extend and avoid mistakes?
The idea is that by extending the class, I end on the safe side of the 0/3/5 rules.
I have this in mind, which apparently works.
class NOCOPYNOMOVE {
NOCOPYNOMOVE(NOCOPYNOMOVE &v) = delete;
NOCOPYNOMOVE(NOCOPYNOMOVE &&v) = delete;
NOCOPYNOMOVE& operator=(NOCOPYNOMOVE &r) = delete;
NOCOPYNOMOVE& operator=(NOCOPYNOMOVE &&r) = delete;
};
class Foo: private NOCOPYNOMOVE {
public:
Foo() {}
~Foo() {}
};
Don't need to delete move constructor, see https://stackoverflow.com/a/38820178/2945027
Avoid ALL_CAPS name, as it is conventionally by every convention used for macros
There's no reason to omit const in copy constructor/assignment, so the usual form should be preferred
If classes would inherit from some NOCOPYNOMOVE in some namespace, it may trigger unintended ADL. boost::noncopyable solves it by putting the definition in noncopyable_ namespace, see the implementation
I'd prefer just not having some base, instead spelling out these two or three lines in the target class:
class Foo {
public:
Foo() {}
~Foo() {}
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
};

Storing a class that uses the PIMPL idiom in a std::vector

I am writing an application that needs to store objects of a class that uses the PIMPL idiom in a std::vector. Because the class uses std::unique_ptr to store a pointer to it's implementation and std::unique_ptr is not copyable, the class itself is not copyable. std::vector should still work in this case because the class is still movable.
To avoid creating a copy I tried using emplace_back to construct the elements directly into the vector, but for some reason it still complains that it is trying to call the copy constructor!
I've written a simple example to demonstrate the problem.
test.h:
#pragma once
#include <memory>
// Simple test class implemented using the PIMPL (pointer to implementation) idiom
class Test
{
public:
Test(const int value);
~Test();
void DoSomething();
private:
// Forward declare implementation struct
struct Impl;
// Pointer to the implementation
std::unique_ptr<Impl> m_impl;
};
test.cpp
#include "test.h"
#include <iostream>
// Implementation struct definition
struct Test::Impl
{
Impl(const int value)
: m_value(value)
{}
void DoSomething()
{
std::cout << "value = " << m_value << std::endl;
}
private:
int m_value;
};
// Construct the class and create an instance of the implementation struct
Test::Test(const int value)
: m_impl(std::make_unique<Impl>(value))
{}
Test::~Test() = default;
// Forward function calls to the implementation struct
void Test::DoSomething()
{
m_impl->DoSomething();
}
main.cpp:
#include "test.h"
#include <vector>
int main()
{
std::vector<Test> v;
// Even though I'm using "emplace_back" it still seems to be invoking the copy constructor!
v.emplace_back(42);
return 0;
}
When I try to compile this code I get the following error:
error C2280: 'Test::Test(const Test &)': attempting to reference a deleted function
This leads to two questions...
Why is it attempting to use the copy constructor even though I explicitly used emplace_back?
How can I get this to compile without errors? The object is movable so according to the standard it should be able to be stored in a std::vector.
Add Test(Test&&) noexcept; and Test& operator=(Test&&) noexcept; then =default them in your cpp file.
You should probably make Test(const int value) explicit while you are at it.
In modern gcc/clang at least, you can =default the move ctor in the header. You cannot do this to operator= because it can delete the left hand side pointer, nor the zero argument constructor (something to do with constructing the default deleter?) or destructor (which has to call the default deleter).

Why does default move constructor need default-deleter of class used in unique_ptr?

The following code correctly compiles with Visual Studio 2013:
#include <memory>
namespace NS
{
class SomeOtherClass;
class MyClass
{
public:
MyClass();
virtual ~MyClass();
private:
std::unique_ptr<SomeOtherClass> m_someOtherClass;
};
}
int main()
{
auto mc = NS::MyClass();
}
This is because of a bug in Visual Studio 2013, in which the initialization of mc in main is directly optimized without checking for a move constructor.
In Visual Studio 2015 this doesn't compile because the move constructor must exist, so we change the code to this:
#include <memory>
namespace NS
{
class SomeOtherClass;
class MyClass
{
public:
MyClass();
virtual ~MyClass();
MyClass(MyClass&&) = default;
private:
std::unique_ptr<SomeOtherClass> m_someOtherClass;
};
}
int main()
{
auto mc = NS::MyClass();
}
And again this compiles.
But, if we now want to export the DLL, then compilation again fails. This is the modified code:
#include <memory>
namespace NS
{
class SomeOtherClass;
class __declspec(dllexport) MyClass
{
public:
MyClass();
virtual ~MyClass();
MyClass(MyClass&&) = default;
private:
std::unique_ptr<SomeOtherClass> m_someOtherClass;
};
}
int main()
{
auto mc = NS::MyClass();
}
This is a part of the compiler output:
memory(1193): error C2027: use of undefined type 'NS::SomeOtherClass'
test.cpp(5): note: see declaration of 'NS::SomeOtherClass'
...
memory(1194): error C2338: can't delete an incomplete type
memory(1195): warning C4150: deletion of pointer to incomplete type 'NS::SomeOtherClass'; no destructor called
It seems that the default-generated move-constructor requires being able to destruct SomeOtherClass. This is strange because MyClass has a destructor, in which the full definition of SomeOtherClass is known.
So why doesn't this compile when exporting the DLL? And why does the default move constructor require knowing the definition of SomeOtherClass?
std::unique_ptr requires a complete type, specifically to handle the deletion.
Your defaulted move constructor is inline and could be represented by the following pseudocode:
MyClass(MyClass&& other):
m_someOtherClass(std::move(other.m_someOtherClass));
{}
This requires SomeOtherClass to be a complete type, to be able to move the default deleter templated on it.
MSDN on defining inline C++ functions with dllexport
You can define as inline a function with the dllexport attribute. In
this case, the function is always instantiated and exported, whether
or not any module in the program references the function. The function
is presumed to be imported by another program.
I don't have VS2015 handy, but just declaring the constructor in the class and defining it in a translation unit where SomeOtherClass is defined should do the trick:
class __declspec(dllexport) MyClass
{
public:
MyClass();
virtual ~MyClass();
MyClass(MyClass&&);
private:
std::unique_ptr<SomeOtherClass> m_someOtherClass;
};
}
file_containing_~MyClass.cpp
MyClass::MyClass(MyClass&&)=default;

Is it possible to declare operator= private and have it synthesized by the compiler at the same time in C++

I am happy with the operator =, which is synthesized by the compiler automatically. But I want it to be private and do not want to bloat my code with page long definitions of the type
Foo& Foo::operator= (const Foo& foo)
{
if (this == &foo)
return *this;
member1_ = foo.member1_;
member2_ = foo.member2_;
member3_ = foo.member2_;
...
member1000_ = foo.member1000_;
return *this;
}
Please, is there a way to do this?
In C++11 it is:
class Foo
{
Foo& operator=(const Foo& source) = default;
public:
// ...
};
Unfortunately, most compilers haven't implemented this part of the new standard yet.
Another option is to use the Pimpl idiom.
class Foo {
public:
Foo() : pImpl(new FooImpl) {}
// ... Foo's public interface, same as before
private:
Foo& operator=(const Foo& source); //- Foo's assignment operator is private
struct FooImpl;
boost::scoped_ptr<FooImpl> pImpl;
};
struct FooImpl {
// ... all the private data members that use to be in Foo
// Note: using the compiler generated copy assignment operator
};
The copy assignment operator is private from the POV of Foo clients but you can still leverage the compiler generated copy assignment via FooImpl. The tradeoff comes when implementing Foo's member functions as you now have to access the data through the pImpl pointer.

Pimpl not working

This is a very noobish mistake, but I dont know whats happening here.
There are loads of pimpl examples but I dont understand why this isn't working (this was one of the examples more or less but I dont see the difference).
I have a very simple Pimpl example, but it wont work.
// Foo.hpp
#include <boost/scoped_ptr.hpp>
class Foo
{
struct Bar;
//boost::scoped_ptr<Bar> pImpl;
Bar* pImpl;
public:
Foo();
~Foo() {}
int returnValue();
private:
};
and
// Foo.cpp
#include "foo.hpp"
struct Foo::Bar
{
Bar() {}
~Bar() {}
int value;
};
Foo::Foo() : pImpl(new Bar())
{
pImpl->value = 7;
}
int Foo::returnValue() {
return *pImpl->value;
}
Compiling this gives me the error.
C2100: illegal indirection.
Thanks.
int returnValue() should be a member function:
// vvvvv
int Foo::returnValue() {
return pImpl->value; // no need to dereference, value isn't a pointer
}
You need to define your constructor, copy-constructor, copy assignment operator, and destructor after the implementation class has been defined. (Otherwise the implicit destructor is dangerous, and scoped_ptr won't let you do that):
// Foo.hpp
#include <boost/scoped_ptr.hpp>
class Foo
{
struct Bar;
boost::scoped_ptr<Bar> pImpl;
public:
Foo();
~Foo();
int returnValue(); // could be const (so should be)
private:
// just disable copying, like scoped_ptr
Foo(const Foo&); // not defined
Foo& operator=(const Foo&); // not defined
};
And:
// Foo.cpp
#include "foo.hpp"
struct Foo::Bar
{
int value;
};
Foo::Foo() :
pImpl(new Bar())
{
pImpl->value = 7;
}
Foo::~Foo()
{
// okay, Bar defined at this point; scoped_ptr can work
}
int Foo::returnValue()
{
return pImpl->value;
}
As an aside, you may have a problem using boost::scoped_ptr for a pImpl because your pImpl is forwardly declared and you may find that the class needs to be fully visible in order to call scoped_ptr's destructor (which deletes the underlying).
Some compilers will allow you to work around this by putting the body of your destructor in the compilation unit (the .cpp file) where the class is visible.
The simplest solution is that if your destructor has to be implemented anyway you may as well just use a raw pointer and have your destructor delete it. And if you want to use something from boost to help you, derive your outer class from boost::noncopyable. Otherwise ensure you handle copy-construction and assignment properly.
You can use shared_ptr to your pImpl. You can then copy your outer class around happily although they share the same underlying unless you overload the copy-constructor and assignment operator to do otherwise.