Manually define only part of copy constructor and assignment operator - c++

I'm wondering if there is a way to implement copy constructors and assignment operators such that only a small modification is needed when these are redefined for a class.
For example, consider a class as such:
class Foo {
private:
int* mInt_ptr;
/* many other member variables
of different types that aren't
pointers */
public:
Foo();
Foo(const Foo&);
Foo& operator=(const Foo&);
~Foo();
};
Now, in order to deal with the pointer mInt_ptr I would need to handle it appropriately in the copy constructor and assignment operator. However, the rest of the member variables are safe to do a shallow copy of. Is there a way to do this automatically?
Once a class becomes large it may become tedious and unwieldy to explicitly write out the operations to copy the non-pointer member variables, so I'm wondering if there is a way to write, say, a copy constructor such as:
Foo::Foo(const Foo& tocopy)
{
mInt_ptr = new int(*tocopy.mInt_ptr);
/* Do shallow copy here somehow? */
}
rather than the explicit form of:
Foo::Foo(const Foo& tocopy)
{
mInt_ptr = new int(*tocopy.mInt_ptr);
mVar1 = tocopy.mVar1;
mVar2 = tocopy.mVar2;
...
...
mVarN = tocopy.mVarN;
}

Generally, don't use raw pointers, for exactly the reason that you're now fighting with. Instead, use a suitable smart pointer, and use copy-swap assignment:
class Foo
{
int a;
Zip z;
std::string name;
value_ptr<Bar> p;
public:
Foo(Foo const &) = default;
Foo & operator=(Foo rhs)
{
rhs.swap(*this);
return *this;
}
void swap(Foo & rhs)
{
using std::swap;
swap(a, rhs.a);
swap(z, rhs.z);
swap(name, rhs.name);
swap(p, rhs.p);
}
};
namespace std { template <> void swap<Foo>(Foo & a, Foo & b) { a.swap(b); } }
The value_ptr could be a full-blown implementation, or something simple such as this:
template <typename T> // suitable for small children,
class value_ptr // but not polymorphic base classes.
{
T * ptr;
public:
constexpr value_ptr() : ptr(nullptr) { }
value_ptr(T * p) noexcept : ptr(p) { }
value_ptr(value_ptr const & rhs) : ptr(::new T(*rhs.ptr)) { }
~value_ptr() { delete ptr; }
value_ptr & operator=(value_ptr rhs) { rhs.swap(*this); return *this; }
void swap(value_ptr & rhs) { std::swap(ptr, rhs.ptr); }
T & operator*() { return *ptr; }
T * operator->() { return ptr; }
};

How about you wrap all the shallow-copy bits in a small helper struct and use the default copy behaviour there.
class Foo {
private:
int* mInt_ptr;
struct helper_t
/* many other member variables
of different types that aren't
pointers */
} mHelper;
public:
Foo();
Foo(const Foo&);
Foo& operator=(const Foo&);
~Foo();
};
Foo::Foo(const Foo& tocopy)
{
mInt_ptr = new int(*tocopy.mInt_ptr);
mHelper = tocopy.mHelper;
}
Using better primitives, as Kerrek suggested, seems like better design though. This is just another possibility.

Regardless if you use raw pointers or smart pointers the Kerrek's solution is right in the sense that you should make a copy constructor, destructor and swap and implement assignment using those:
class Foo
{
private:
int* mInt_ptr;
// many other member variables
// of different types
public:
Foo()
: mInt_ptr(NULL)
// initialize all other members
{}
Foo(const Foo& that)
: mInt_ptr(new int(*that.mInt_ptr) )
// copy-construct all other members
{}
Foo& operator=(const Foo& that)
{
// you may check if(this == &that) here
Foo(that).swap(*this);
return *this;
}
~Foo()
{
delete mInt_ptr;
// and release other resources
}
void swap(Foo& that)
{
std::swap(mInt_ptr, that.mInt_ptr);
// swap all members
}
};
The members are inline here just to keep it compact, usually it is not advisable to burden class definition with inline member definitions.

Related

How can I forbid a simple assignment MyClass=int?

if I have a class with attributes like this:
struct MyClass {
double **arowofpointers;
int capacity;
};
Now, if the task says "make sure this line of code in the main function is legal:
MyClass a(10); //makes a variable whose type is MyClass that has the capacity of 10
But make sure that the following line of code in the main function is not legal:
MyClass a=10;
Still, the following line of your code in the main function should be legal:
a=b+c;
where, a,b and c are all variables whose type is MyClass.
Which constructors should I make? Is there anything I should set on delete or something?
Constructing an instance of type MyClass like this
MyClass a(10);
requires a constructor that takes an integer parameter:
class MyClass {
public:
MyClass(int param);
// ...
};
But as constructors are implicit by default (which is unfortunate), allowing for MyClass a = 10;, you need to make it explicit:
// This constructor must be called explicitly via MyClass(int)
explicit MyClass(int param);
This will make the compiler complain when it encounters MyClass a = 10;.
For the operator part of your question, you might want to have a look at this (the "Arithmetic Operators" part).
MyClass a(10); requires a conversion constructor that takes an integer as input. To prevent MyClass a=10;, make this constructor explicit.
a = b + c; requires an operator+ to concatenate two MyClass objects, and an operator= to assign one MyClass object to another. If you want to support initializations like MyClass a = b;, MyClass a = b + c;, etc, you will also need a copy constructor as well.
And don't forget a destructor.
So, you will need these constructors and operators in your struct:
struct MyClass
{
private:
double **arowofpointers;
int capacity;
public:
// default constructor
MyClass();
// conversion constructor
explicit MyClass(int cap);
// copy constructor
MyClass(const MyClass &src);
// move constructor (C++11 and later only, optional but recommended)
MyClass(MyClass &&src);
// destructor
~MyClass();
// copy assignment operator
MyClass& operator=(const MyClass &rhs);
// move assignment operator(C++11 and later only, optional but recommended)
MyClass& operator=(MyClass &&rhs);
// concatenation operator overload
MyClass operator+(const MyClass &rhs) const;
// compound concatenation assignment operator (optional)
MyClass& operator+=(const MyClass &rhs);
// swap helper
void swap(MyClass &other);
};
// std::swap() overload
void swap(MyClass &lhs, MyClass &rhs);
Where the implementations might look something like this:
#include <algorithm>
MyClass::MyClass()
: arowofpointers(nullptr), capacity(0)
{
}
MyClass::MyClass(int cap)
: arowofpointers(new double*[cap]), capacity(cap)
{
std::fill_n(arowofpointers, capacity, nullptr);
}
MyClass::MyClass(const MyClass &src)
: arowofpointers(new double*[src.capacity]), capacity(src.capacity)
{
std::copy(src.arowofpointers, src.arowofpointers + capacity, arowofpointers);
}
MyClass::MyClass(MyClass &&src)
: arowofpointers(nullptr), capacity(0)
{
src.swap(*this);
}
MyClass::~MyClass()
{
delete[] arowofpointers;
}
MyClass& MyClass::operator=(const MyClass &rhs)
{
if (&rhs != this)
MyClass(rhs).swap(*this);
return *this;
}
MyClass& MyClass::operator=(MyClass &&rhs)
{
MyClass tmp(std::move(*this));
rhs.swap(*this);
return *this;
}
MyClass MyClass::operator+(const MyClass &rhs) const
{
MyClass tmp(capacity + rhs.capacity);
std::copy(arowofpointers, arowofpointers + capacity, tmp.arowofpointers);
std::copy(rhs.arowofpointers, rhs.arowofpointers + rhs.capacity, tmp.arowofpointers + capacity);
return tmp;
}
MyClass& MyClass::operator+=(const MyClass &rhs)
{
MyClass tmp = *this + rhs;
tmp.swap(*this);
return *this;
}
void swap(MyClass &lhs, MyClass &rhs)
{
lhs.swap(rhs);
}
That being said, if you use std::vector instead, then you don't need to handle most of this yourself, let the compiler and STL to the heavy work for you:
#include <vector>
struct MyClass
{
private:
std::vector<double*> arowofpointers;
public:
MyClass();
explicit MyClass(int cap);
MyClass operator+(const MyClass &rhs) const;
MyClass& operator+=(const MyClass &rhs);
void swap(MyClass &other);
};
void swap(MyClass &lhs, MyClass &rhs);
#include <algorithm>
MyClass::MyClass()
: arowofpointers()
{
}
MyClass::MyClass(int cap)
: arowofpointers(cap, nullptr)
{
}
MyClass MyClass::operator+(const MyClass &rhs) const
{
MyClass tmp(arowofpointers.capacity() + rhs.arowofpointers.capacity());
tmp.arowofpointers.insert(tmp.arowofpointers.end(), arowofpointers.begin(), arowofpointers.end();
tmp.arowofpointers.insert(tmp.arowofpointers.end(), rhs.arowofpointers.begin(), rhs.arowofpointers.end();
return tmp;
}
MyClass& MyClass::operator+=(const MyClass &rhs)
{
MyClass tmp = *this + rhs;
tmp.swap(*this);
return *this;
}
void swap(MyClass &lhs, MyClass &rhs)
{
lhs.swap(rhs);
}

Should I delete the move constructor and the move assignment of a smart pointer?

I'm implementing a simple smart pointer, which basically keeps track of the number of references to a pointer that it handles.
I know I could implement move semantics, but I don't think it makes sense as copying a smart pointer is very cheap. Especially considering that it introduces opportunities to produce nasty bugs.
Here's my C++11 code (I omitted some inessential code). General comments are welcome as well.
#ifndef SMART_PTR_H_
#define SMART_PTR_H_
#include <cstdint>
template<typename T>
class SmartPtr {
private:
struct Ptr {
T* p_;
uint64_t count_;
Ptr(T* p) : p_{p}, count_{1} {}
~Ptr() { delete p_; }
};
public:
SmartPtr(T* p) : ptr_{new Ptr{p}} {}
~SmartPtr();
SmartPtr(const SmartPtr<T>& rhs);
SmartPtr(SmartPtr<T>&& rhs) =delete;
SmartPtr<T>& operator=(const SmartPtr<T>& rhs);
SmartPtr<T>& operator=(SmartPtr<T>&& rhs) =delete;
T& operator*() { return *ptr_->p_; }
T* operator->() { return ptr_->p_; }
uint64_t Count() const { return ptr_->count_; }
const T* Raw() const { return ptr_->p_; }
private:
Ptr* ptr_;
};
template<typename T>
SmartPtr<T>::~SmartPtr() {
if (!--ptr_->count_) {
delete ptr_;
}
ptr_ = nullptr;
}
template<typename T>
SmartPtr<T>::SmartPtr(const SmartPtr<T>& rhs) : ptr_{rhs.ptr_} {
++ptr_->count_;
}
template<typename T>
SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<T>& rhs) {
if (this != &rhs) {
if (!--ptr_->count_) {
delete ptr_;
}
ptr_ = rhs.ptr_;
++ptr_->count_;
}
return *this;
}
#endif // SMART_PTR_H_
Guideline
Never delete the special move members.
In typical code (such as in your question), there are two motivations to delete the move members. One of those motivations produces incorrect code (as in your example), and for the other motivation the deletion of the move members is redundant (does no harm nor good).
If you have a copyable class and you don't want move members, simply don't declare them (which includes not deleting them). Deleted members are still declared. Deleted members participate in overload resolution. Members not present don't. When you create a class with a valid copy constructor and a deleted move member, you can't return it by value from a function because overload resolution will bind to the deleted move member.
Sometimes people want to say: this class is neither movable nor copyable. It is correct to delete both the copy and the move members. However just deleting the copy members is sufficient (as long as the move members are not declared). Declared (even deleted) copy members inhibit the compiler from declaring move members. So in this case the deleted move members are simply redundant.
If you declare deleted move members, even if you happen to pick the case where it is redundant and not incorrect, every time someone reads your code, they need to re-discover if your case is redundant or incorrect. Make it easier on readers of your code and never delete the move members.
The incorrect case:
struct CopyableButNotMovble
{
// ...
CopyableButNotMovble(const CopyableButNotMovble&);
CopyableButNotMovble& operator=(const CopyableButNotMovble&);
CopyableButNotMovble(CopyableButNotMovble&&) = delete;
CopyableButNotMovble& operator=(CopyableButNotMovble&&) = delete;
// ...
};
Here is example code you probably expected to work with CopyableButNotMovble but will fail at compile time:
#include <algorithm>
#include <vector>
struct CopyableButNotMovble
{
// ...
CopyableButNotMovble(const CopyableButNotMovble&);
CopyableButNotMovble& operator=(const CopyableButNotMovble&);
CopyableButNotMovble(CopyableButNotMovble&&) = delete;
CopyableButNotMovble& operator=(CopyableButNotMovble&&) = delete;
CopyableButNotMovble(int);
// ...
friend bool operator<(CopyableButNotMovble const& x, CopyableButNotMovble const& y);
};
int
main()
{
std::vector<CopyableButNotMovble> v{3, 2, 1};
std::sort(v.begin(), v.end());
}
In file included from test.cpp:1:
algorithm:3932:17: error: no
matching function for call to 'swap'
swap(*__first, *__last);
^~~~
algorithm:4117:5: note: in
instantiation of function template specialization 'std::__1::__sort<std::__1::__less<CopyableButNotMovble,
CopyableButNotMovble> &, CopyableButNotMovble *>' requested here
__sort<_Comp_ref>(__first, __last, __comp);
^
algorithm:4126:12: note: in
instantiation of function template specialization 'std::__1::sort<CopyableButNotMovble *,
std::__1::__less<CopyableButNotMovble, CopyableButNotMovble> >' requested here
_VSTD::sort(__first, __last, __less<typename iterator_traits<_RandomAccessIterator>::value_type>());
^
...
(many nasty error messages from deep inside your std::lib)
The correct way to do this is:
struct CopyableButNotMovble
{
// ...
CopyableButNotMovble(const CopyableButNotMovble&);
CopyableButNotMovble& operator=(const CopyableButNotMovble&);
// ...
};
The redundant case:
struct NeitherCopyableNorMovble
{
// ...
NeitherCopyableNorMovble(const NeitherCopyableNorMovble&) = delete;
NeitherCopyableNorMovble& operator=(const NeitherCopyableNorMovble&) = delete;
NeitherCopyableNorMovble(NeitherCopyableNorMovble&&) = delete;
NeitherCopyableNorMovble& operator=(NeitherCopyableNorMovble&&) = delete;
// ...
};
The more readable way to do this is:
struct NeitherCopyableNorMovble
{
// ...
NeitherCopyableNorMovble(const NeitherCopyableNorMovble&) = delete;
NeitherCopyableNorMovble& operator=(const NeitherCopyableNorMovble&) = delete;
// ...
};
It helps if you make a practice of always grouping all 6 of your special members near the top of your class declaration, in the same order, skipping those you don't want to declare. This practice makes it easier for readers of your code to quickly determine that you have intentionally not declared any particular special member.
For example, here is the pattern I follow:
class X
{
// data members:
public:
// special members
~X();
X();
X(const X&);
X& operator=(const X&);
X(X&&);
X& operator=(X&&);
// Constructors
// ...
};
Here is a more in-depth explanation of this declaration style.

Reference counted structure exposing a POD interface

I currently have a structure that declares a copy constructor, and a destructor, and keeps a reference count to a pimpl object. This allows me to pass this structure around by value. I need to refactor it because I want it to have a POD interface because it's part of a library that now needs to be used from code that uses an older C++ flavor.
Because of this, I have to remove both the copy constructor and the desctructor. I can't figure out how I can keep this nice "pass by value", multiple ownership mechanism after I have removed that. Shared pointers are not an option because the structure is used as parameter to the method of other classes that also need to be seen as POD from the perspective of the user of the library.
struct Foo {
Foo(const Foo&);
const Foo& operator=(const Foo&);
~ Foo();
private:
void* pimpl;
};
struct MainLibrary {
void DoSomething(Foo param);
}
The user code now is like:
MainLibrary ml;
{
Foo a;
ml.doSomething(a);
}
at this point, the a variable can be kept for a long time inside the main library. For efficiency, the internals of Foo can't be deep copied each time, that's why the pimpl implementation keeps a reference counter that is incremented each time the instance of Foo is copied, decremented each time the instance of Foo is destroyed.
Too much for a comment... something like below. Client include foo03.h and their usage should remain unaffected. Your C++11 implementation is invoked via a "C" layer. You can find lots of examples if you search for "exposing C++ code to C" or similar....
foo03.h:
extern "C"
{
void* foo_constructor();
void* foo_constructor2(void* rhs);
void foo_assign(void* lhs, void* rhs);
void foo_destructor(void* p_this);
}
struct Foo {
Foo() { p_ = foo_constructor(); }
Foo(const Foo& rhs) { p_ = foo_constructor2(rhs.p_); }
const Foo& operator=(const Foo& rhs) { foo_assign(p_, rhs.p_); return *this; }
~Foo() { foo_destructor(p_); }
private:
void* p_;
};
foo11.h:
// up to you whether you call this Foo (relying on mangling differences to avoid conflicts when
// linking with C++03 objects), FooImpl, put it in a namespace ala Impl::Foo etc..
struct FooImpl {
FooImpl() { ... }
FooImpl(const FooImpl&) { ... }
const FooImpl& operator=(const FooImpl& rhs) { ... }
~FooImpl() { ... }
};
extern "C"
{
void* foo_constructor() { return new FooImpl(); }
void* foo_constructor2(void* rhs) { return new FooImpl(*(FooImpl*)rhs); }
void foo_assign(void* lhs, void* rhs) { *(FooImpl*)lhs = *(FooImpl*)rhs; }
void foo_destructor(void* p_this) { delete (FooImpl*)p_this; }
}

Initializer lists: copy constructors and assignment operators = redundancy?

It seems that initalizer lists are a good idea for your class constructors and, I'm assuming, for the copy constructor as well. For the assignment operator one has to assign each member in the body of the function. Consider the following simple block:
class Foo {
private:
int a,b;
public:
Foo(int c, int d) : a(c), b(d) {}
Foo(const Foo & X) : a(X.a), b(X.b) {}
Foo& operator=(const Foo& X) {
if (this == &X) return *this;
a = X.a;
b = X.b;
return *this;
}
};
If a class has a moderate amount of data members, there are three places where one can mess up the the different assignments/initialization. By that I mean, what if the copy constructor looked like:
Foo(const Foo & X) : a(X.a), b(X.a) {}
or a line was missing from the operator=. Since the assignment operator and the copy constructor often have the same effect (in that we copy members from one Foo to another) can I "reuse" the code from the copy constructor or the assignment operator or vice versa?
Your goal should be to not write copy constructors/assignment operators at all. Your goal should be to let the compiler do it. Standard library containers are all copyable, so use them where reasonable.
If there are members that cannot be copied correctly, then use smart pointers or other RAII objects. Those objects are the ones that should need special copy constructors/assignments. And they only need them for their one member.
Everything else should not use them.
Since the assignment operator and the copy constructor often have the same effect.
Not at all, one does initialization while the other does assignment. They are different in the initial state of the object, and their tasks are separate (though similar). The canonical assignment operator is usually done as:
Foo& operator=(Foo right) {
right.swap( *this );
return *this;
}
It might not be valid to forward all to an assignment operator, but that is was commmon in C++03 where it was allowed.
In C++11 constructors are easier: forward all the constructors to one master constructor.
class Foo {
private:
int a,b;
public:
Foo(int c, int d) : a(c), b(d) {}
Foo(const Foo & X) : Foo(x.a, x.d) {}
//syntax may be wrong, I don't have a C++11 compiler
Foo& operator=(const Foo& X) {
if (this == &X) return *this;
a = X.a;
b = X.b;
return *this;
}
}
In C++03 (where it is allowed)
class Foo {
private:
int a,b;
void init(int c, int d) {a=c; b=d;}
public:
Foo(int c, int d) : {init(c,d);}
Foo(const Foo & X) : {init(X.a, X.b);}
Foo& operator=(const Foo& X) { init(X.a, X.b);}
}
But keep in mind that this cannot be used in some common cases. (any object that isn't assignable)

STL-friendly pImpl class?

I am maintaining a project that can take a considerable time to build so am trying to reduce dependencies where possible. Some of the classes could make use if the pImpl idiom and I want to make sure I do this correctly and that the classes will play nicely with the STL (especially containers.) Here is a sample of what I plan to do - does this look OK? I am using std::auto_ptr for the implementation pointer - is this acceptable? Would using a boost::shared_ptr be a better idea?
Here is some code for a SampleImpl class that uses classes called Foo and Bar:
// SampleImpl.h
#ifndef SAMPLEIMPL_H
#define SAMPLEIMPL_H
#include <memory>
// Forward references
class Foo;
class Bar;
class SampleImpl
{
public:
// Default constructor
SampleImpl();
// Full constructor
SampleImpl(const Foo& foo, const Bar& bar);
// Copy constructor
SampleImpl(const SampleImpl& SampleImpl);
// Required for std::auto_ptr?
~SampleImpl();
// Assignment operator
SampleImpl& operator=(const SampleImpl& rhs);
// Equality operator
bool operator==(const SampleImpl& rhs) const;
// Inequality operator
bool operator!=(const SampleImpl& rhs) const;
// Accessors
Foo foo() const;
Bar bar() const;
private:
// Implementation forward reference
struct Impl;
// Implementation ptr
std::auto_ptr<Impl> impl_;
};
#endif // SAMPLEIMPL_H
// SampleImpl.cpp
#include "SampleImpl.h"
#include "Foo.h"
#include "Bar.h"
// Implementation definition
struct SampleImpl::Impl
{
Foo foo_;
Bar bar_;
// Default constructor
Impl()
{
}
// Full constructor
Impl(const Foo& foo, const Bar& bar) :
foo_(foo),
bar_(bar)
{
}
};
SampleImpl::SampleImpl() :
impl_(new Impl)
{
}
SampleImpl::SampleImpl(const Foo& foo, const Bar& bar) :
impl_(new Impl(foo, bar))
{
}
SampleImpl::SampleImpl(const SampleImpl& sample) :
impl_(new Impl(*sample.impl_))
{
}
SampleImpl& SampleImpl::operator=(const SampleImpl& rhs)
{
if (this != &rhs)
{
*impl_ = *rhs.impl_;
}
return *this;
}
bool SampleImpl::operator==(const SampleImpl& rhs) const
{
return impl_->foo_ == rhs.impl_->foo_ &&
impl_->bar_ == rhs.impl_->bar_;
}
bool SampleImpl::operator!=(const SampleImpl& rhs) const
{
return !(*this == rhs);
}
SampleImpl::~SampleImpl()
{
}
Foo SampleImpl::foo() const
{
return impl_->foo_;
}
Bar SampleImpl::bar() const
{
return impl_->bar_;
}
You should consider using copy-and-swap for assignment if it's possible that Foo or Bar might throw as they're being copied. Without seeing the definitions of those classes, it's not possible to say whether they can or not. Without seeing their published interface, it's not possible to say whether they will in future change to do so, without you realising.
As jalf says, using auto_ptr is slightly dangerous. It doesn't behave the way you want on copy or assignment. At a quick look, I don't think your code ever allows the impl_ member to be copied or assigned, so it's probably OK.
If you can use scoped_ptr, though, then the compiler will do that tricky job for you of checking that it's never wrongly modified. const might be tempting, but then you can't swap.
There are a couple of problems with the Pimpl.
First of all, though not evident: if you use Pimpl, you will have to define the copy constructor / assignment operator and destructor (now known as "Dreaded 3")
You can ease that by creating a nice template class with the proper semantic.
The problem is that if the compiler sets on defining one of the "Dreaded 3" for you, because you had used forward declaration, it does know how to call the "Dreaded 3" of the object forward declared...
Most surprising: it seems to work with std::auto_ptr most of the times, but you'll have unexpected memory leaks because the delete does not work. If you use a custom template class though, the compiler will complain that it cannot find the needed operator (at least, that's my experience with gcc 3.4.2).
As a bonus, my own pimpl class:
template <class T>
class pimpl
{
public:
/**
* Types
*/
typedef const T const_value;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
/**
* Gang of Four
*/
pimpl() : m_value(new T) {}
explicit pimpl(const_reference v) : m_value(new T(v)) {}
pimpl(const pimpl& rhs) : m_value(new T(*(rhs.m_value))) {}
pimpl& operator=(const pimpl& rhs)
{
pimpl tmp(rhs);
swap(tmp);
return *this;
} // operator=
~pimpl() { delete m_value; }
void swap(pimpl& rhs)
{
pointer temp(rhs.m_value);
rhs.m_value = m_value;
m_value = temp;
} // swap
/**
* Data access
*/
pointer get() { return m_value; }
const_pointer get() const { return m_value; }
reference operator*() { return *m_value; }
const_reference operator*() const { return *m_value; }
pointer operator->() { return m_value; }
const_pointer operator->() const { return m_value; }
private:
pointer m_value;
}; // class pimpl<T>
// Swap
template <class T>
void swap(pimpl<T>& lhs, pimpl<T>& rhs) { lhs.swap(rhs); }
Not much considering boost (especially for the cast issues), but there are some niceties:
proper copy semantic (ie deep)
proper const propagation
You still have to write the "Dreaded 3". but at least you can treat it with value semantic.
EDIT: Spurred on by Frerich Raabe, here is the lazy version, when writing the Big Three (now Four) is a hassle.
The idea is to "capture" information where the full type is available and use an abstract interface to make it manipulable.
struct Holder {
virtual ~Holder() {}
virtual Holder* clone() const = 0;
};
template <typename T>
struct HolderT: Holder {
HolderT(): _value() {}
HolderT(T const& t): _value(t) {}
virtual HolderT* clone() const { return new HolderT(*this); }
T _value;
};
And using this, a true compilation firewall:
template <typename T>
class pimpl {
public:
/// Types
typedef T value;
typedef T const const_value;
typedef T* pointer;
typedef T const* const_pointer;
typedef T& reference;
typedef T const& const_reference;
/// Gang of Five (and swap)
pimpl(): _holder(new HolderT<T>()), _p(this->from_holder()) {}
pimpl(const_reference t): _holder(new HolderT<T>(t)), _p(this->from_holder()) {}
pimpl(pimpl const& other): _holder(other->_holder->clone()),
_p(this->from_holder())
{}
pimpl(pimpl&& other) = default;
pimpl& operator=(pimpl t) { this->swap(t); return *this; }
~pimpl() = default;
void swap(pimpl& other) {
using std::swap;
swap(_holder, other._holder);
swap(_p, other._p)
}
/// Accessors
pointer get() { return _p; }
const_pointer get() const { return _p; }
reference operator*() { return *_p; }
const_reference operator*() const { return *_p; }
pointer operator->() { return _p; }
const_pointer operator->() const { return _p; }
private:
T* from_holder() { return &static_cast< HolderT<T>& >(*_holder)._value; }
std::unique_ptr<Holder> _holder;
T* _p; // local cache, not strictly necessary but avoids indirections
}; // class pimpl<T>
template <typename T>
void swap(pimpl<T>& left, pimpl<T>& right) { left.swap(right); }
I've been struggling with the same question. Here's what I think the answer is:
You can do what you are suggesting, so long as you define the copy and assignment operators to do sensible things.
It's important to understand that the STL containers create copies of things. So:
class Sample {
public:
Sample() : m_Int(5) {}
void Incr() { m_Int++; }
void Print() { std::cout << m_Int << std::endl; }
private:
int m_Int;
};
std::vector<Sample> v;
Sample c;
v.push_back(c);
c.Incr();
c.Print();
v[0].Print();
The output from this is:
6
5
That is, the vector has stored a copy of c, not c itself.
So, when you rewrite it as a PIMPL class, you get this:
class SampleImpl {
public:
SampleImpl() : pimpl(new Impl()) {}
void Incr() { pimpl->m_Int++; }
void Print() { std::cout << m_Int << std::endl; }
private:
struct Impl {
int m_Int;
Impl() : m_Int(5) {}
};
std::auto_ptr<Impl> pimpl;
};
Note I've mangled the PIMPL idiom a bit for brevity. If you try to push this into a vector, it still tries to create a copy of the SampleImpl class. But this doesn't work, because std::vector requires that the things it store provide a copy constructor that doesn't modify the thing it's copying.
An auto_ptr points to something that is owned by exactly one auto_ptr. So when you create a copy of an auto_ptr, which one now owns the underlying pointer? The old auto_ptr or the new one? Which one is responsible for cleaning up the underlying object? The answer is that ownership moves to the copy and the original is left as a pointer to nullptr.
What auto_ptr is missing that prevents its use in a vector is copy constructor taking a const reference to the thing being copied:
auto_ptr<T>(const auto_ptr<T>& other);
(Or something similar - can't remember all the template parameters). If auto_ptr did provide this, and you tried to use the SampleImpl class above in the main() function from the first example, it would crash, because when you push c into the vector, the auto_ptr would transfer ownership of pimpl to the object in the vector and c would no longer own it. So when you called c.Incr(), the process would crash with a segmentation fault on the nullptr dereference.
So you need to decide what the underlying semantics of your class are. If you still want the 'copy everything' behaviour, then you need to provide a copy constructor that implements that correctly:
SampleImpl(const SampleImpl& other) : pimpl(new Impl(*(other.pimpl))) {}
SampleImpl& operator=(const SampleImpl& other) { pimpl.reset(new Impl(*(other.pimpl))); return *this; }
Now when you try to take a copy of a SampleImpl, you also get a copy of its Impl struct, owned by the copy SampleImpl. If you're taking an object that had lots of private data members and was used in STL containers and turning it into a PIMPL class, then this is probably what you want, as it provides the same semantics as the original. But note that pushing the object into a vector will be considerably slower as there is now dynamic memory allocation involved in copying the object.
If you decide you don't want this copy behaviour, then the alternative is for the copies of SampleImpl to share the underlying Impl object. In this case, it's not longer clear (or even well-defined) which SampleImpl object owns the underlying Impl. If ownership doesn't clearly belong in one place, then std::auto_ptr is the wrong choice for storing it
and you need to use something else, probably a boost template.
Edit: I think the above copy constructor and assignment operator are exception-safe so long as ~Impl doesn't throw an exception. This should always be true of your code anyway.