Joh*_*com 9 c++ unique-ptr c++11
我还没有看到一个同时使用unique_ptr和move-semantics的pimpl示例.
我想将一个CHelper类添加到STL派生容器中,并使用pimpl来隐藏CHelper所做的事情.
这看起来不错吗?
class CDerived : public set<CSomeSharedPtr>, public CHelper
{
//...
};
Run Code Online (Sandbox Code Playgroud)
`
// 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);
};
Run Code Online (Sandbox Code Playgroud)
//===========================
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;
}
Run Code Online (Sandbox Code Playgroud)
考虑到 的唯一成员CHelper是 aunique_ptr并且 copy 的默认实现是调用基数和成员的副本,move 的默认实现是调用基数和成员的移动,因此无需重写CHelperctor并分配。只需让默认值完成其工作即可。他们只会调用适当的 unique_ptr 移动构造函数和运算符。
关于将CHelper和 a放在一起set<...>形成 CDerived...这不是“规范设计”(set不是 OOP 类...,CHelper 也不是),但如果使用得当就可以工作(不要尝试分配 aCDerived给一个CHelper*广告打电话删掉就可以了,不然你会落泪的)。只是没有足够的信息来了解它们的目的。
如果问题是“我希望 CHelper 也能够复制”,那么您可能最好遵循这样的习惯用法(#include并且using 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;
};
Run Code Online (Sandbox Code Playgroud)
当然,您可以根据需要随意分离声明和实现。
根据约翰·巴尔科姆的评论进行编辑。
是的,代码当然会改变,但其实质内容不会改变。您只需将声明转发struct impl;到CHelper(以便 unique_ptr 有意义),然后CHelper::impl在其他地方声明 struct (可能在将完成所有 CHelper 实现的 CPP 文件中)。
这里唯一需要注意的是,在 CPP 文件中 CHelper::impl 必须同时定义构造函数和析构函数,以便在 CPP 文件内具有连贯的 unique_ptr 实例化(必须调用 impl 析构函数)。否则,某些编译器可能会在包含 CHelper 声明的所有文件中出现“类型使用不完整”错误。
关于第二点(源自std::set),这是 C++ 编程中一个有争议的方面。由于与 C++ 本身无关的原因,但在面向对象编程学派中,“继承”意味着“是 a”,而“是 a”意味着“可以进行对象替换”。正因为如此,由于如果基 dtor 不是虚拟的,则通过基指针删除对象是 UB,从而使对象替代 UB,OOP 学派拒绝将任何没有虚拟 dtor 的类的继承作为教条,并且因为这种方式他们在开始编程时就受过教育,如果你这样做,他们就会开始向你吐火。
对我来说,这在你的设计中不是问题,但是他们错误地理解了 C++ 继承并不意味着“是一个”,而是“就像”并且并不意味着对象替换(对我来说,他们错误地认为每个 C++ 类都是一个 OOP 对象,而不是你使用工具做一些对你有用的事情,如果你想更多地澄清我的立场,只需看这里或这里:C++ 中的对象替换不是“对象”,而是逐个方法,因为每个方法都可以是虚拟的或不独立的)。也就是说,也许你必须与这些人一起工作,所以......评估不跟随他们实践他们自己最喜欢的宗教的利弊。