我还没有看到同时使用unique_ptr和move-semantics的pimpl示例。

我想将CHelper类添加到STL派生的容器中,并使用pimpl隐藏CHelper的功能。

这看起来正确吗?

派生

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

`

助手
// 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;
}

最佳答案

考虑到CHelper的唯一成员是unique_ptr,并且copy的默认实现是调用基础和成员的副本,move的默认实现是调用基础和成员的move,因此无需覆盖CHelper指导和分配。只需让默认值完成工作即可。他们只会调用适当的unique_ptr move构造函数和运算符。

关于将CHelperset<...>放在一起以形成CDerived ...这不是“规范设计”(set不是OOP类,而CHelper也不是),但是如果使用得当,可以正常工作(不要尝试)将CDerived分配给CHelper*广告调用,将其删除,否则您将流泪。只是没有足够的信息来了解它们的用途。

如果问题是“我希望CHelper也能够复制”,那么您应该更好
遵循这样的习惯用法(#includeusing namespace分开...)

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;
};

当然,可以根据需要随时将声明和实现分开。

在John Balcom发表评论后编辑。

是的,代码当然会更改,但本质上不会更改。
您只需要将struct impl;声明为CHelper(这样unique_ptr就可以了),然后声明
在其他地方构造CHelper::impl(可能在将完成所有CHelper实现的CPP文件中)。

这里唯一需要注意的是,在CPP文件中,CHelper::impl必须同时定义构造函数和析构函数,以便在CPP文件中具有一致的unique_ptr实例化(必须调用impl析构函数)。否则,对于某些编译器来说,存在所有包含CHelper声明的文件中都会出现“类型使用不完整”错误的风险。

关于第二点(源自std::set),这是C++编程的一个有争议的方面。
出于与C++本身无关的原因,但是在面向对象编程学派中,“继承”的意思是"is",而"is"的意思是“可以进行对象替换”。
因此,由于如果基本dtor不是虚拟的,则通过基本指针删除对象是UB,从而使对象成为UB,因此OOP学校拒绝教条继承任何没有虚拟dtor的类的继承,并且是因为他们在开始编程时就已经受过教育,如果您这样做,他们会开始向您吐火。

对我来说,这不是设计中的问题,但是他们理解C++继承并不意味着"is"而是“像”,并不意味着对象替换(对我而言,这是他们认为每个C++类都是一个OOP对象,而不是您使用工具来做对您有用的事情,如果您想进一步说明我的立场,请看看herehere:C++中的对象替换不是针对“对象”,而是逐个方法,因为每个方法都可以虚拟的或彼此独立的)。就是说,也许您必须与这些人一起工作,所以……在不偏爱他们自己喜欢的宗教的情况下,评估优缺点。

09-06 11:29