众所周知在创建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就能解决问题吗?
templatefriend 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
这种做法有一个缺陷:如果Widget是一个final类,这种继承将不被允许。如果实在不行还是用new吧…
我们可以将这一段代码提取出来作为一个新的类Enable_shared_create以方便使用:
templateclass 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



