【问题标题】:Is this the right way to implement pimpl wth unique_ptr and move-semantics in C++11这是在 C++11 中使用 unique_ptr 和 move-semantics 实现 pimpl 的正确方法吗
【发布时间】:2012-06-28 01:02:43
【问题描述】:

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

我想向 STL 派生容器添加一个 CHelper 类,并使用 pimpl 隐藏 CHelper 所做的事情。

这看起来对吗?

派生的.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;  
}  

【问题讨论】:

  • GOTW 100 就是这样。
  • class CDerived : public set&lt;CSomeSharedPtr&gt; 这是一个非常糟糕的起点。标准容器没有被设计为扩展,不要从它们继承,而是使用组合。
  • @DavidRodríguez-dribeas:使用合成能解决问题吗?这两件事之间有什么关系?继承和组合,由代表构造/销毁赋值的工作方式相同。唯一的问题是通过基指针删除派生的。但这与这里提出的问题完全无关!

标签: c++ c++11 unique-ptr


【解决方案1】:

考虑到CHelper 的唯一成员是unique_ptr,并且副本的默认实现是调用基数和成员的副本,move 的默认实现是调用基础和成员的 move,不需要覆盖 CHelper ctors 和 assigns。让默认值完成他们的工作。他们只会调用适当的 unique_ptr 移动构造函数和运算符。

关于将 CHelperset&lt;...&gt; 组合在一起以形成 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 cmets 之后编辑。

是的,当然代码会发生变化,但实质上不会发生变化。 您只需将 declare struct impl; 转发到 CHelper (这样 unique_ptr 就有意义),然后声明 struct CHelper::impl 其他地方(可能在 CPP 文件中,所有 CHelper 实现都将在其中完成)。

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

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

对我来说,这在您的设计中不是问题,但他们理解 C++ 继承并不意味着“是一个”而是“就像”的错误,并且确实并不意味着对象替换(对我来说,他们认为每个 C++ 类都是 OOP 对象是他们的错,而不是您使用工具来做对您有用的事情,如果您想了解更多关于我的说明,请查看 herehere位置:C++ 中的对象替换不是“对象”而是逐个方法,因为每个方法都可以是虚拟的,也可以不是彼此独立的)。话虽如此,可能您必须与这些人一起工作,所以...评估在他们自己喜欢的宗教实践中不跟随他们的利弊。

【讨论】:

  • std::unique_ptr 是不可复制的,所以如果CHelper 是可复制的,用户必须提供一个复制构造函数。由于提供了复制构造函数,编译器不会隐式生成移动构造函数……所以是的,它是必需的。
  • 需要移动构造函数,但可以是= default;
  • 请注意,上述所有 cmets 都是在编辑此答案之前编写的。
  • 您的响应代码包含 struct impl {}。当我只想隐藏一些证明数据时,我以前做过这些。在这种情况下,我想使用一个类,这样我就可以隐藏私有方法和数据。这会改变您的响应代码吗?
  • 另外,在“CDerived : public set...”类上 - 我这样做是为了直接访问 set 成员 CDerived->remove()。 set 可以很容易地成为 CDerived 的成员,并且 CHelper 可以从 CObject 派生,因此它与“oop”兼容。即使我永远不需要实例化 CHelper,这会是一个更好的设计吗?
猜你喜欢
  • 2021-01-05
  • 1970-01-01
  • 1970-01-01
  • 2012-11-11
  • 2019-09-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-13
  • 1970-01-01
相关资源
最近更新 更多