在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

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
std::string_view是C++17的新类型,它是一个用来表示字符串的轻量级视图,不拥有字符串的所有权,而是指向一个已有的字符串对象。它类似于指向字符串的指针,但是提供了更多的安全性和便利性。 std::string_view的定义如下: ``` namespace std { template<class charT, class traits = std::char_traits<charT>> class basic_string_view { public: using value_type = charT; using traits_type = traits; using pointer = value_type*; using const_pointer = const value_type*; using reference = value_type&; using const_reference = const value_type&; using const_iterator = const_pointer; using iterator = const_iterator; using size_type = std::size_t; using difference_type = std::ptrdiff_t; // 构造函数 constexpr basic_string_view() noexcept; constexpr basic_string_view(const basic_string_view&) noexcept = default; constexpr basic_string_view(const charT* str); constexpr basic_string_view(const charT* str, size_type len); // 迭代器 constexpr const_iterator begin() const noexcept; constexpr const_iterator end() const noexcept; constexpr const_iterator cbegin() const noexcept; constexpr const_iterator cend() const noexcept; // 容量 constexpr size_type size() const noexcept; constexpr size_type length() const noexcept; constexpr size_type max_size() const noexcept; constexpr bool empty() const noexcept; // 元素访问 constexpr const_reference operator[](size_type pos) const noexcept; constexpr const_reference at(size_type pos) const; constexpr const_reference front() const noexcept; constexpr const_reference back() const noexcept; constexpr const_pointer data() const noexcept; // 子串 constexpr basic_string_view substr(size_type pos, size_type n = npos) const; // 查找 constexpr size_type find(basic_string_view v, size_type pos = 0) const noexcept; constexpr size_type find(charT c, size_type pos = 0) const noexcept; constexpr size_type rfind(basic_string_view v, size_type pos = npos) const noexcept; constexpr size_type rfind(charT c, size_type pos = npos) const noexcept; constexpr size_type find_first_of(basic_string_view v, size_type pos = 0) const noexcept; constexpr size_type find_first_of(charT c, size_type pos = 0) const noexcept; constexpr size_type find_last_of(basic_string_view v, size_type pos = npos) const noexcept; constexpr size_type find_last_of(charT c, size_type pos = npos) const noexcept; // 操作符 friend bool operator==(basic_string_view lhs, basic_string_view rhs) noexcept; friend bool operator!=(basic_string_view lhs, basic_string_view rhs) noexcept; friend bool operator<(basic_string_view lhs, basic_string_view rhs) noexcept; friend bool operator<=(basic_string_view lhs, basic_string_view rhs) noexcept; friend bool operator>(basic_string_view lhs, basic_string_view rhs) noexcept; friend bool operator>=(basic_string_view lhs, basic_string_view rhs) noexcept; // 常量 static constexpr size_type npos = size_type(-1); }; // 类型别名 using string_view = basic_string_view<char>; using wstring_view = basic_string_view<wchar_t>; using u16string_view = basic_string_view<char16_t>; using u32string_view = basic_string_view<char32_t>; } ``` std::string_view的使用非常简单,可以通过构造函数传入一个已有的字符串对象,也可以传入一个C风格字符串(以'\0'结尾)。然后就可以像操作字符串一样使用std::string_view了,例如获取字符串的长度、访问字符串的字符、查找字符串等等。和std::string不同的是,std::string_view没有修改字符串的操作。 std::string_view的好处在于它比std::string更轻量级,不需要额外的内存分配和复制操作,因此在一些需要高效处理字符串的场景下,使用std::string_view可以减少不必要的开销。例如,在函数参数传递字符串时,可以使用std::string_view来避免不必要的内存分配和复制,提高程序的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值