栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

在friend中让std::make

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

在friend中让std::make

众所周知在创建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 spw1(new Widget{42}); // ok
  auto spw2 = std::make_shared(42);     // Error
}

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

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

  template 
  friend std::shared_ptr std::make_shared(Args &&...);

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

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

class Widget {
  friend void fun();

  Widget() = default;
  Widget(int) {}
  Widget(double, double) {}
  Widget(const std::string &) {}
  template 
  static std::shared_ptr create(Args &&...args) {
    struct make_shared_helper : public Widget {
      make_shared_helper(Args &&...a) : Widget(std::forward(a)...) {}
    };
    return std::make_shared(std::forward(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的构造函数是public的。最后我们利用向上转型将std::shared_ptr转型为std::shared_ptr

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

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

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

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

 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声明为friend。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/878947.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号