众所周知在创建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
}
由于fun
是Widget
的friend
,在这里直接用new
并调用Widget
的构造函数自然是没有问题的。但如果想用std::make_shared
就不行,因为std::make_shared
不是Widget
的friend
。
难道把std::make_shared
声明为Widget
的friend
就能解决问题吗?
template <typename T, typename ...Args>
friend std::shared_ptr<T> 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 <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
。