在friend中让std::make_shared使用private构造函数

众所周知在创建std::shared_ptr对象的时候,我们总是应该优先选择std::make_shared而非手动地用new。《Effective Modern C++》中提到了若干种std::make_shared不奏效的情况,主要是如下几种:

  • make系列函数不支持定制deleter
  • 大括号初始化物无法被完美转发
  • 由于weak_ptr的存在导致控制块和对象所占的内存被延迟释放

在实际操作中,我们还遇到了一种特殊的情况:假设有一个类Widget的构造函数是private的,我们想在它的friend函数中动态创建一个Widget对象并移入智能指针:

class Widget {
  friend void fun();

  // private constructors
  Widget() = default;
  Widget(int) {}
  Widget(double, double) {}
  Widget(const std::string &) {}
};

void fun() {
  std::shared_ptr<Widget> spw1(new Widget{42}); // ok
  auto spw2 = std::make_shared<Widget>(42);     // Error
}

由于funWidgetfriend,在这里直接用new并调用Widget的构造函数自然是没有问题的。但如果想用std::make_shared就不行,因为std::make_shared不是Widgetfriend

难道把std::make_shared声明为Widgetfriend就能解决问题吗?

  template <typename T, typename ...Args>
  friend std::shared_ptr<T> std::make_shared(Args &&...);

并不能,由于标准库函数之间的层层嵌套调用,对于Widget构造函数的调用可能根本不是发生在make_shared中。如果你想寻根究底地找下去,那自然是可以的,但最终你会获得一份移植性低的代码。(事实上,在C++11下和在C++20下这个调用就发生在不同的函数中。)

经过一番思索(思考+搜索),我目前找到的解决方案如下:首先我们定义一个factory函数,按照惯例它是一个名为createstatic函数,它将参数转发给std::make_shared,那么我们在外部只需调用create即可。但这样仍然没有改变“Widget的构造函数对make_shared不可见”这一事实,我们需要一些黑魔法:

class Widget {
  friend void fun();

  Widget() = default;
  Widget(int) {}
  Widget(double, double) {}
  Widget(const std::string &) {}
  template <typename... Args>
  static std::shared_ptr<Widget> create(Args &&...args) {
    struct make_shared_helper : public Widget {
      make_shared_helper(Args &&...a) : Widget(std::forward<Args>(a)...) {}
    };
    return std::make_shared<make_shared_helper>(std::forward<Args>(args)...);
  }
};

void fun() {
  auto p1 = Widget::create(42);
  auto p2 = Widget::create(3.14, 6.28);
  auto p3 = Widget::create("hello");
}

我们在Widget::create中定义了一个局部类make_shared_helper,继承自Widget,这个局部类的构造函数将参数转发给Widget的构造函数。由于它的定义在Widget类内,它可以访问Widget的私有构造函数。之后,我们调用std::make_shared创建一个std::shared_ptr<make_shared_helper>,这是可以的,因为make_shared_helper的构造函数是public的。最后我们利用向上转型将std::shared_ptr<make_shared_helper>转型为std::shared_ptr<Widget>

这种做法有一个缺陷:如果Widget是一个final类,这种继承将不被允许。如果实在不行还是用new吧…

我们可以将这一段代码提取出来作为一个新的类Enable_shared_create以方便使用:

template <typename Class>
class Enable_shared_create {
 protected:
  template <typename... Args>
  static std::shared_ptr<Class> create(Args &&...args) {
    struct make_shared_helper : public Class {
      make_shared_helper(Args &&...a) : Class(std::forward<Args>(a)...) {}
    };
    return std::make_shared<make_shared_helper>(std::forward<Args>(args)...);
  }
};

class Widget : private Enable_shared_create<Widget> {
  friend void fun();
  friend class Enable_shared_create<Widget>;

 private:
  Widget(int) {}
  Widget(double, double) {}
  Widget() {}
  Widget(const std::string &) {}
};

void fun() {
  auto p = Widget::create(42);
  auto p2 = Widget::create(3.14, 6.28);
  auto p3 = Widget::create("hello");
}

我们甚至可以private继承自它,因为需要调用create的代码是friend。注意,不要忘记将Enable_shared_create<Widget>声明为friend

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值