Implementing swap/copy functions: is there a better way? - c++

These are how I implemented the swap and copy functions for my queue.
Is there a better way?
template <class T>
void Queue<T>::copy(Queue<T> const & other)
{
if(this == &other)
return;
if(m_size != 0)
this->clear();
this->m_cap = other.m_cap;
this->enqueue(other);
}
template <class T>
void Queue<T>::swap(Queue<T> const & other)
{
if(this == &other)
return;
std::swap(this->m_front, other.m_front);
std::swap(this->m_back, other.m_back);
std::swap(this->m_size, other.m_size);
std::swap(this->m_cap, other.m_cap);
}
Thank you:)

Instead of a copy method, you should instead implement a copy constructor. It would be strange for the copy constructor to be passed an instance of itself. If you insist on checking, you can use an assertion.
template <class T>
Queue<T>::Queue(Queue<T> const & other)
: m_cap(other.m_cap), m_size(0), m_front(), m_back()
{
assert(&other != this);
enqueue(other);
}
Your copy method is actually an assignment. It is more natural to implement an assignment operator. This can be accomplished by following the copy-swap idiom.
template <class T>
Queue<T> & Queue<T>::operator = (Queue<T> other)
{
swap(*this, other);
return *this;
}
There is also an idiomatic way to implement swap (taught to me by Mooing Duck a long time ago):
template <class T>
class Queue {
//...
friend void swap(Queue &a, Queue &b) {
using std::swap;
swap(a.m_front, b.m_front);
swap(a.m_back, b.m_back);
swap(a.m_size, b.m_size);
swap(a.m_cap, b.m_cap);
}
};
This way, you can use argument dependent lookup (ADL) to pick the type specific swap implementation if available. And now Queue itself has such an implementation, which is used by the assignment operator. But it can also be used in the case Queue is put inside an object that wants to implement swap for itself.

Related

Unable to move temporary object into the *this object

While trying to create a Binary Search Tree (BST) using C++11 I hit a snag.
I can't extract a properly created BST out of the function where it is created.
The function reads data from a file (in this case just a number on each line) and builds a BST from those. The building part in the loop works correctly.
The problem is I can't get the temporary object moved where I want it to be.
Zooming in on the problem I think, see edit 3.
More context:
BST<T> is a class that derives publicly from std::unique_ptr<BSTknoop<T>>
BST<T> also inherits the constructors of unique_ptr<BSTknoop<T>> with
template<class T>
using BSTknoopptr=std::unique_ptr<BSTknoop<T>>; // alias template
and
using BSTknoopptr<T>::BSTknoopptr; // in the body of the BST<T> class declaration`
A BSTknoop<T> is a data structure with 3 fields
one T object to hold the node data
two BST<T> objects left and right (each child is a tree in its own right)
the goal is to move my newly created BST into the calling object. The idea is that in main you can call BST<int> tree; tree.lees(ifs); (with ifs an open input filestream) and tree holds the properly filled BST afterwards.
The function:
template<class T>
istream& BST<T>::lees(istream& is){
string lijn;
T knoopwaarde;
getline(is,lijn);
knoopwaarde = stoi(lijn);
BST<T> temptree(new BSTknoop<T>());
temptree->sl = knoopwaarde;
for(int i=1; i<DATA_SET_LENGTH ; i++){
getline(is,lijn);
knoopwaarde=stoi(lijn);
temptree.add(knoopwaarde);
cout<<temptree;
}
this->swap(temptree); /* This does not work */
/* *this = move(temptree); this does not work either*/
return is;
}
I have also tried to return a BST<T> from the function and moving that result in the main. That didn't work either.
The program crashes at runtime with an unknown signal.
Side note: I'm unsure as to why BST<T> temptree(new BSTknoop<T>()); works in regard to the templating. The construction works because BST<T> inherits the constructors for unique_ptr<BSTknoop<T>>
edit 1: the declaration of the BST<T> class:
template <class T>
class BST:public BSTknoopptr<T>{
using BSTknoopptr<T>::BSTknoopptr;
public:
friend istream& operator>>(istream& is, BST<T>& bb){
return bb.lees(is);
}
friend ostream& operator<<(ostream& os, const BST<T>& bb){
//return bb.schrijflevelorder(os);
return bb.schrijfKnoop(os);
}
void add(const T&);
ostream& schrijf(ostream&);
ostream& schrijfKnoop(ostream&) const;
int aantalSleutels() const;
istream& lees(istream&);
ostream& schrijflevelorder(ostream& os) const;
private:
};
the declaration of theBSTknoop<T> class:
template <class T>
class BSTknoop{
friend class BST<T>;
public:
BSTknoop() {}
explicit BSTknoop(T _sl) : sl(_sl) {}
private:
T sl;
BST<T> links,rechts;
};
edit 2: I've written a move constructor and move assignment operator. I've made sure to retain the default constructor as well using BST<T>()=default; I'm confused though: the BST class doesn't have any members for which I would have to implement my own move constr / operator.
However, BST has inherited from unique_ptr<BSTknoop<T>>so implicitly, it must hold a member of that type. Suppose I want to keep the inheritance, is there any neat way to make this work?
Is is also true that I can't (or perhaps shouldn't) implement a copy constr / operator as those are deleted for unique_ptr?
template<class T>
BST<T>::BST(BST<T>&& other){
*this = move(other);
}
template<class T>
BST<T>& BST<T>::operator=(BST<T>&& other){
cout<<"called move operator BST"<<endl;
(*this).BSTknoopptr<T>::operator=(move(other));
return *this;
}
edit 3: with my own move constr / operator, the temptree no longer gets filled properly. Below is my code for add which is used to build the tree. If I omit my own move constr / operator, then this works. What I need is an operation that works like this->get()->links = move(tmp).
types this->get()->links => BST<T>
tmp => BST<T>
How come this works? It does the operation I need, why can't I use something similar to do *this = move(temptree) by writing the operation myself.
template<class T>
void BST<T>::add(const T& value){
if(value <= this->get()->sl){
if(this->get()->links != nullptr){
this->get()->links.add(value);
}
else {
BST<T> tmp(new BSTknoop<T>(value));
this->get()->links = move(tmp);
}
}
else{
if(this->get()->rechts != nullptr){
this->get()->rechts.add(value);
}
else{
BST<T> tmp(new BSTknoop<T>(value));
this->get()->rechts = move(tmp);
}
}
}
Okay, I made the stupidest mistake. There is nothing wrong with the inheritance and accompanying move semantics. I used the DATA_SET_LENGTH preprocessor directive inside my istream& BST<T>::lees(istream& is) function. While my final dataset will hold a million items. My dataset for testing purposes only holds 12. I forgot to change the value of DATA_SET_LENGTH so stoi(lijn) crashed.
For anyone interested in the solution:
notes
You don't need to write much code to make this work.
It is possible to write BST<T>& operator=(BSTknoopptr<T>&&); yourself and it works. However even without explicitly writing your own it also works.
The BST class has no fields of pointer type so we don't get into trouble with unique_ptr not having a virtual destructor
Final question (perhaps someone can comment):
The inherited operator operator=(BSTknoopptr<T>&&), what does its signature look like in BST<T>? Mostly in regards to return type (if it's now a member of BST<T> what type does it return?).
** TLDR: why can I rely on the inherited move operator / move constructors of unique_ptr<BSTknoop<T>>? **
bst.h
#define DATA_SET_LENGTH 12
using namespace std;
template <class T>
class BST;
template <class T>
class BSTknoop;
template<class T>
using BSTknoopptr=std::unique_ptr<BSTknoop<T>>;
template <class T>
class BST:public BSTknoopptr<T>{
using BSTknoopptr<T>::BSTknoopptr;
public:
BST<T>& operator=(BST<T>&&) = default;
BST<T>(BST<T>&&) = default;
BST<T>()=default;
//BST<T>& operator=(BSTknoopptr<T>&&);
friend istream& operator>>(istream& is, BST<T>& bb){
return bb.lees(is);
}
friend ostream& operator<<(ostream& os, const BST<T>& bb){
//return bb.schrijflevelorder(os);
return bb.schrijfKnoop(os);
}
void add(const T&);
//schrijf schrijft uit in een vorm die min of meer menselijk leesbaar is
ostream& schrijf(ostream&);
ostream& schrijfKnoop(ostream&) const;
int aantalSleutels() const;
istream& lees(istream&);
//schrijflevelorder schrijft uit in een vorm die door lees(...) kan gelezen worden.
ostream& schrijflevelorder(ostream& os) const;
private:
};
template <class T>
class BSTknoop{
friend class BST<T>;
public:
BSTknoop() {}
explicit BSTknoop(T _sl) : sl(_sl) {}
private:
T sl;
BST<T> links,rechts;
};
//template<class T>
//BST<T>& BST<T>::operator=(BSTknoopptr<T>&& other){
// cout<<"called BST<T> move with BSTknoopptr r-value ref argument"<<endl;
// (*this).BSTknoopptr<T>::operator=(move(other));
// return *this;
//}
template<class T>
void BST<T>::add(const T& value){
if(value <= this->get()->sl){
if(this->get()->links != nullptr){
this->get()->links.add(value);
}
else {
//BSTknoopptr<T> tmp(new BSTknoop<T>(value)); if used with my own BST<T>& BST<T>::operator=(BSTknoopptr<T>&& other)
BST<T> tmp(new BSTknoop<T>(value));
this->get()->links = move(tmp);
}
}
else{
if(this->get()->rechts != nullptr){
this->get()->rechts.add(value);
}
else{
//BSTknoopptr<T> tmp(new BSTknoop<T>(value)); if used with my own BST<T>& BST<T>::operator=(BSTknoopptr<T>&& other)
BST<T> tmp(new BSTknoop<T>(value));
this->get()->rechts = move(tmp);
}
}
}
template<class T>
istream& BST<T>::lees(istream& is){
string lijn;
T knoopwaarde;
getline(is,lijn);
knoopwaarde = stoi(lijn);
BST<T> temptree(new BSTknoop<T>());
temptree->sl = knoopwaarde;
for(int i=1; i<DATA_SET_LENGTH ; i++){
getline(is,lijn);
knoopwaarde=stoi(lijn);
temptree.add(knoopwaarde);
cout<<temptree;
}
*this = move(temptree);
return is;
}
main.cpp
int main(){
ifstream ifs;
ifs.open("c.dat");
if(ifs.is_open()){
BST<int> tree;
tree.lees(ifs);
tree.schrijfKnoop(cout);
}
else {
cerr<<"failed to open file"<<endl;
return -1;
}
}

assignment operator template and copy constructor in c++

so basically i m trying to use the assignment operator in way to allocate 2 vars :
S solutionCourante, bestSolution; //(S is a template class)
bestSolution = solutionCourante = solutionInitiale;
Here is the operator i'm dealing with :
template <class S, class T>
const Graphe<S,T> & Graphe<S,T>::operator = (const Graphe<S,T> & graphe)
{
this->lSommets = graphe.lSommets->copieListe(graphe.lSommets);
this->lAretes = graphe.lAretes->copieListe(graphe.lAretes);
return *this;
}
Here is my copy constructor :
template <class S, class T>
Graphe<S,T>::Graphe(const Graphe<S,T> & graphe)
{
*this = graphe;
}
(I know the constructor copy is a bit bad coded but works)
So at any time, i can see that "bestSolution" and "solutionCourante" are not NULL but empty, and i don't understand why because in my operator "monGraphe" is filled. So it seems like i m doing something wrong when returning the value, first time i m trying to do this operator.
According to :
const Graphe<S,T> & Graphe<S,T>::operator = (const Graphe<S,T> & graphe)
graphe is the item i want to copy and we got *this = graphe?
An assignment operator is supposed to assign a value to "this", not allocate a new one.
template <class S, class T>
Graphe<S,T> & Graphe<S,T>::operator = (const Graphe<S,T> & graphe)
{
lSommets = graphe.lSommets ? new PElement<Sommet<T>>(*graphe.lSommets) : nullptr;
lAretes = graphe.lAretes ? new PElement<Arete<S,T>>(*graphe.lAretes) : nullptr;
prochaineClef = graphe.prochaineClef;
return *this;
}
template <class S, class T>
Graphe<S,T>::Graphe(const Graphe<S,T> & graphe)
{
*this = graphe;
}
Generally speaking, you should not return something allocated on the heap with new, because any ownership information is lost. You should probably try to use smart pointers such as std::unique_ptr.
An answer was already posted, but uses a method of having the assignment operator doing most of the work.
Since you already coded the copy constructor, your assignment operator should be written using the copy/swap idiom: What is the copy-and-swap idiom?
What's usually done (if you want synergy between assignment operator and copy constructor) is to have the copy constructor do the bulk of the work, while the assignment operator utilizes the copy constructor (and destructor).
Here is your code using copy/swap:
#include <algorithm>
//...
template <class S, class T>
class Graphe
{
//...
friend void swap(Graphe<S,T>& lhs, Graphe<S,T>& rhs)
{
std::swap(lhs.lAretes, rhs.lAretes);
std::swap(lhs.lSommets, rhs.lSommets);
std::swap(lhs.prochaineClef, rhs.prochaineClef);
}
//...
};
//...
template <class S, class T>
Graphe<S,T>::Graphe(const Graphe<S,T> & graphe) :
{
lSommets = graphe.lSommets ? new PElement<Sommet<T>>(*graphe.lSommets) : nullptr;
lAretes = graphe.lAretes ? new PElement<Arete<S,T>>(*graphe.lAretes) : nullptr;
prochaineClef = graphe.prochaineClef;
}
template <class S, class T>
Graphe<S,T>& Graphe<S,T>::operator = (Graphe<S,T> graphe)
{
swap(*this, graphe);
return *this;
}
A function called swap was added to the template class that merely swaps all of the members between the left and right hand parameter. I emphasize all in the event that you didn't post all of your class members.
Assuming that your copy constructor is bug free, and your destructor is working and is bug-free, the code above will work correctly.
Edit: Made swap a friend function, as suggested by comments from T.C.

Implement a generic stack container as an adaptor class template

I'm having some trouble in my Data Structures (cop 4530) class. I need to "Implement a generic Stack container as an adaptor class template". My template and the implementation are in two different files stack.h and stack.hpp (this is required). I wanted to do this Stack set up as an Array, but it seems as though my teacher set it us up to do it as a linked list if I'm not wrong?? I'm just confused on how to get started setting up the stack as an Array if someone could explain it to me (we have a very useless book). And all I really need is someone to explain to me just a few of the functions from ".hpp" where I implement my Stack code. Particularly the copy / move functions. (If it's better or easier for me to do it a different way than I'm trying then please share)
Here's Stack.h; These are all interfaces of the "Stack class Template" we needed to include
#include <iostream>
#define MAX_SIZE 100
namespace cop4530{
template<typename T>
class Stack{
private:
int A[MAX_SIZE];
int top;
public:
Stack();
~Stack();
Stack (const Stack<T>&);
Stack(Stack<T> &&);
Stack<T>& operator= (const Stack <T>&);
Stack<T> & operator=(Stack<T> &&);
bool empty() const;
void clear();
void push(const T& x);
void push(T && x);
void pop();
T& top();
const T& top() const;
int size() const;
void print(std::ostream& os, char ofc = ' ') const;
}
//non-member global functions
std::ostream& operator<< (std::ostream& os, const Stack<T>& a);
bool operator== (const Stack<T>&, const Stack <T>&);
bool operator!= (const Stack<T>&, const Stack <T>&);
bool operator< (const Stack<T>& a, const Stack <T>& b);
#include "Stack.hpp"
}
#endif
And here's the separate "Stack.hpp" file that holds the implementation of some of them.
#include "stack.h"
#include <iostream>
namespace cop4530{
template<typename T>
Stack<T>::Stack(){
//zero argument constructor
}
template<typename T>
Stack<T>::~Stack(){ //destructor
clear();
}
template<typename T>
Stack<T>::Stack(const Stack<T>&){ //copy constructor
}
template<typename T>
Stack<T>::Stack(Stack<T> &&){ //move constructor
}
template<typename T>
Stack<T>::Stack<T> & Stack<T>::operator=(const Stack<T> &){
//copy assignment operator=
}
template<typename T>
Stack<T>::Stack<T> & Stack<T>::operator=(Stack<T> &&){
//move assignment operator=
}
/*does this look right? I don't think so...*/
template<typename T>
void Stack<T>::push(const T& x){ //adds x to the stack, copy version
insert(begin(), x);
}
template<typename T>
void Stack<T>::push(T && x){ // adds x to the stack, move version
insert(begin(), std::move(val));
}
To implement the Stack as a adapter class for array you should really start from the basic functionality of a stack. Let's consider a few basics:
constructor- created an empty stack
isFull/isEmpty - return true/false depending on how much space is available in your stack.
push - adds one element at the top of the queue
pop - removes the first element from the top of the queue
Let's start with the constructor: you could say that your stack is empty when top == -1. Since your array is statically allocated (you can consider to dynamically allocate memory for the array in the constructor), all you need to do is top =-1 and you have yourself an empty stack.
isFull/isEmpty now become obvious:
isEmpty: return top == -1
isFull: return top == MAX_SIZE
PUSH: adds one elements to the top of the stack:
if (!isFull)
{
top++;
A[top] = new_element;
}
I'll let you figure out the rest; the idea is to update the stack pointer top and always keep an eye on how much space you have available.

Template cast operator C++

I am trying to make a little wrapper class such as
template <typename T>
class EdgeTriggeredState
{
public:
void Tick()
{
oldData = newData;
}
EdgeTriggeredState& operator =(const T& v)
{
newData = v;
return *this;
}
// T& operator = (void)
// {
// return oldData;
// }
// T& operator T()
// {
// return oldData;
// }
private:
T oldData;
T newData;
};
Basically I want to be able to directly assign to a variable of type T the value wrapped by the class. I have tried implementing both an assignment (to type T) operator and a cast operator to type T. I am a bit rusty on my C++ as I have been working solely in C. Is there a way to go about implementing this without creating a named getter method?
When I uncomment the first implementation attempt I get error
"../EdgeTriggeredState.h:19:21: error: ‘T& EdgeTriggeredState::operator=()’ must take exactly one argument"
When I uncomment the second implementation (and comment out the first) I get error:
"../EdgeTriggeredState.h:24:16: error: return type specified for ‘operator T’"
When you write an operator T, the return type is implicit, so your code should look something like:
template <typename T>
class DumbWrapper {
T oldData;
T newData;
public:
DumbWrapper& operator = (const T& val) {
newData = val;
return *this;
}
operator T() {
return oldData;
}
};
[Also note the semicolon at the end, and the fact that the constructor and conversion operator were probably intended to be public.]

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.