Is there a way to combine the benefits of compiler firewalls (Pimpl) and default-copyability?

不羁的心 提交于 2019-12-05 00:11:16

问题


Suppose I have a class with a private member, which is an implementation detail that clients of the class don't care about. This class is a value type and we want it to be copyable, eg

#include <boost/bimap.hpp>  // some header that pulls in many other files

class MyClass {
public:
    MyClass() {}
    ...
private:
    boost::bimap<Key,Value>   table;
};

Now every client of MyClass is forced to pull in lots of boost headers it doesn't really need, increasing build times. However, the class is at least copyable.

If we introduce a compiler firewall (Pimpl idiom) then we can move the #include dependency to the cpp file, but now we have to do lots more hard work due to the Rule of 5:

// no extra #includes - nice
class MyClass {
public:
    MyClass() {}
    // ugh, I don't want this, just make it copyable!
    MyClass(const MyClass& rhs);
    MyClass(MyClass&& rhs);
    MyClass& operator=(const MyClass& rhs);
    MyClass& operator=(MyClass&& rhs);
    ~MyClass() {}
    ...
private:
    std::unique_ptr<MyClassImpl>  impl;
};

Is there a technique for getting the benefits of the compiler firewall, but retaining copyability so that I don't need to include the Rule of 5 boilerplate?


回答1:


I think the best solution here is to build your own deep-copying smart pointer. If you tailor it to storing Pimpls only, it shouldn't be too difficult:

template <class P>
class pimpl_ptr
{
  std::unique_ptr<P> p_;

public:
  ~pimpl_ptr() = default;

  pimpl_ptr(const pimpl_ptr &src) : p_(new P(*src.p_)) {}
  pimpl_ptr(pimpl_ptr &&) = default;
  pimpl_ptr& operator= (const pimpl_ptr &src) { p_.reset(new P(*src.p_)); return *this; }
  pimpl_ptr& operator= (pimpl_ptr &&) = default;

  pimpl_ptr() = default;
  pimpl_ptr(P *p) : p_(p) {}

  P& operator* () const { return *p_; }
  P* operator-> () const { return &**this; }

  // other ctors and functions as deemed appropriate
};

Just document that it doesn't support pointing to base class subobjects, and you're set. You could enforce this by not giving it a constructor taking a pointer, and enforcing construction through make_pimpl:

template <class P>
class pimpl_ptr
{
  // as above, P* ctor is private
private:
  pimpl_ptr(P *p) : p_(p) {}

  template <class T, class... Arg>
  friend pimpl_ptr<T> make_pimpl(Arg&&... arg);
};

template <class T, class... Arg>
pimpl_ptr<T> make_pimpl(Arg&&... arg)
{
  return pimpl_ptr<T>(new T(std::forward<Arg>(arg)...));
}


来源:https://stackoverflow.com/questions/26378266/is-there-a-way-to-combine-the-benefits-of-compiler-firewalls-pimpl-and-default

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!