9

I haven't yet seen a pimpl example that utilizes both unique_ptr and move-semantics.

I want to add a CHelper class to STL derived containers and use pimpl to hide what CHelper does.

Does this look right?

Derived.h

class CDerived : public set<CSomeSharedPtr>, public CHelper  
{
//...
};

`

Helper.h

// derived containers need to support both copy and move, so CHelper does too  

class CHelper  
{  
private:  
    class impl;  
    unique_ptr<impl> pimpl;  

public:  
//--- default: need both cotr & cotr (complete class) in order to use unique_ptr<impl>  
    CHelper();  
    ~CHelper();  

//--- copy  
    CHelper(const CHelper &src);         //copy constructor  
    CHelper& operator=(const CHelper &src);//assignment operator  

//--- move  
    CHelper(CHelper &&src);         //move constructor  
    CHelper& operator=(CHelper &&src);//move operator  

//--- expose public methods here  
    void SetModified(BOOL bSet=TRUE);  
};  

Helper.cpp

//===========================  
class CHelper::impl  
{  
public:  
BOOL m_bModified; //has the container been modified (needs to be saved)  
// ... other data  

impl() {m_bModified = FALSE;}  

//--- copy cotr/assign  
impl(const impl &src)  
{  
  *this = src;  
}  

void operator=(const impl &src)   
{  
  m_bModified = src.m_bModified;  
  // ...other data  
}  

//--- move cotr/assign ?? do I need to write move cotr/assign ??   

};  

//============================  
CHelper::CHelper() : pimpl(unique_ptr<impl>(new impl())) {}

CHelper::~CHelper() {}  

//--- copy  
CHelper::CHelper(const CHelper &src)  
: pimpl(unique_ptr<impl>(new impl(*src.pimpl))) {}

CHelper& CHelper::operator=(const CHelper &src)  
{  
  if (this != &src)  
    *pimpl = *src.pimpl;  

  return *this;  
}  

//--- move  
CHelper::CHelper(CHelper &&src)  
{  
  if (this != &src)  
  {  
    pimpl = move(src.pimpl); //use move for unique_ptr  
    src.pimpl.reset(nullptr);//leave pimpl in defined / destructable state  
  }  
}  

CHelper& CHelper::operator=(CHelper &&src)  
{  
    if (this != &src)  
    {  
      pimpl = move(src.pimpl); //use 'move' for unique_ptr  
      src.pimpl.reset(nullptr);//leave pimpl in defined / destructable state  
    }  
    return *this;  
}  
juanchopanza
  • 223,364
  • 34
  • 402
  • 480
John Balcom
  • 91
  • 1
  • 2
  • 17
    [GOTW 100](http://herbsutter.com/gotw/_100/) is all about this. – R. Martinho Fernandes Jun 26 '12 at 16:39
  • 4
    `class CDerived : public set` This is a really bad start point. Standard container have not been designed to be extended, do not inherit from them, use composition instead. – David Rodríguez - dribeas Jun 26 '12 at 16:58
  • 2
    @DavidRodríguez-dribeas: does the use of composition solve the problem? What relation is between the two things? Inheritance and composition, by the behalf of construction / destruction assignment works the same. The only problem is deleting the derived through a base pointer. But it is a completely unrelated problem respect to what asked here! – Emilio Garavaglia Jun 26 '12 at 17:09

1 Answers1

4

Considering that the only member of CHelper is a unique_ptr and that the default implementation of copy is calling the copy of the bases and of the members, the default implementation for move is calling the move of the bases and members, there is no need to override the CHelper ctors and assigns. Just let the default do their job. They'll just call the appropriate unique_ptr move constructor and operator.

About putting together CHelper and a set<...> to form a CDerived... that's not a "canonical design" (set is not a OOP class..., and CHelper also isn't) but can work if used properly (don't try to assign a CDerived to a CHelper* ad call delete on it, or you will end in tears). Simply there not enough information to understand what they're purposed.

If the problme is "I whant CHelper to be also able to copy", then you should probably better to folow an idiom like this (#include and using namespace apart ...)

class CHelper
{
    struct impl
    {
       .....
    };
public:
    // create and initialize
    CHelper() :pimpl(new impl) {}

    // move: just keep the default
    CHelper(CHelper&& a) = default;

    // copy: initialize with a copy of impl
    CHelper(const CHelper& a) :pimpl(new impl(*a.pimpl)) {}

    CHelper& operator=(CHelper a) //note: pass by value and let compiler do the magics
    { 
        pimpl = move(a.pimpl); //a now nullifyed, but that's ok, it's just a value
        return *this; 
    }

    ~CHelper() = default; //not really necessary
private:
    unique_ptr<impl> pimpl;
};

Of course, feel free to separate declarations and implementation as you need.

EDIT following John Balcom comments.

Yes, of course the code changes, but not in its substance. You just can forward declare struct impl; into CHelper (so that unique_ptr has a meaning), then declare struct CHelper::impl somewhere else (may be in the CPP file where all CHelper immplementation will be done).

The only attention, here, is that in the CPP file CHelper::impl must have both constructor and destructor defined, in order to have a coherent unique_ptr instantiation (that has to call the impl destructor) inside the CPP file. Otherwise there is the risk, with some compilers, to get an "incomplete type usage" error in all files that include the CHelper declaration.

About the second point (deriving from std::set) this is a controversial aspect of C++ programming. For reason that are not pertinet to C++ itself, but with the Object Oriented Programming school, "inhertance" means "is a", and "is a" means "object subtitution possible". Because of that, since deleting an object via a base pointer is UB if the base dtor isn't virtual, thus making object soubstitution UB, the OOP school refuse as a dogma the inheritance of whatever class having no virtual dtor and because of the way they had been educated when they started to program, they start to spit their flame on you if you do that.

To me, that's not a problem in your design, but their fault in understanding that C++ inheritance does not means "is a" but "is like" and does not imply object substitution (to me it is their fault in thinking every C++ class is an OOP object, not you using a tool to do something useful to you, just look here or here, if you want more clarification on my position: object substitution in C++ is not for "object" but method by method since every method can be virtual or not independently on every other). That's said, may be you have to work with those people, so ... evaluate pros and cons in not following them in the practice of their own favorite religion.

Community
  • 1
  • 1
Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63
  • 2
    `std::unique_ptr` is non-copyable, so if `CHelper` is to be copyable the user must provide a copy constructor. Since a copy constructor is provided, the compiler will not implicitly generate a move constructor... so yes, it is needed. – David Rodríguez - dribeas Jun 26 '12 at 17:07
  • The move constructor is needed, but can be `= default;`. – R. Martinho Fernandes Jun 26 '12 at 17:09
  • Please note all the above comments had been written before this answer had been edited. – Emilio Garavaglia Jun 26 '12 at 19:00
  • Your response code included struct impl {}. I have done those before when I only wanted to hide some provate data. In this case I want to use a class so I can hide private methods and data. Does that change your response code? – John Balcom Jun 27 '12 at 23:26
  • Also, on class "CDerived : public set..." - I did this so I could directly access set<> members CDerived->remove(). set<> could just as easily be a member of CDerived and CHelper could be derived from CObject so it is 'oop' compatible. Would that be a better design even if I would never need instantiate CHelper? – John Balcom Jun 27 '12 at 23:33
  • +1 It's always a pleasure to read your pragmatic views on C++'s inheritance features. – Christian Rau Jun 28 '12 at 08:01
  • NOTE: The [VS2013 compiler](http://msdn.microsoft.com/en-us/library/dn457344.aspx) doesn't support defaulted move constructors nor defaulted move assignments. – orfdorf Jan 03 '15 at 15:51