(可能与如何实现创建新对象的C++方法有关,并返回对它的引用,该引用是关于不同的东西,但在内部包含几乎完全相同的代码)
我想从静态函数返回对静态局部的引用.当然,我可以让它上班,但它并不像我想的那么漂亮.
这可以改善吗?
我有几个类,其没有做很多,除了获得或在一个明确的方式,可靠地初始化资源,然后将其释放.他们甚至不需要了解资源本身,但用户可能仍希望以某种方式查询某些信息.
这当然是琐碎的:
struct foo { foo() { /* acquire */ } ~foo(){ /* release */ } };
int main()
{
foo foo_object;
// do stuff
}
Run Code Online (Sandbox Code Playgroud)
不重要的.或者,这也可以正常工作:
#include <scopeguard.h>
int main
{
auto g = make_guard([](){ /* blah */}, [](){ /* un-blah */ });
}
Run Code Online (Sandbox Code Playgroud)
除了现在,查询东西有点难,而且它不如我喜欢的漂亮.如果你更喜欢Stroustrup而不是Alexandrescu,你可以改为使用GSL并使用一些混合物final_act.随你.
理想情况下,我想写一些类似于:
int main()
{
auto blah = foo::init();
}
Run Code Online (Sandbox Code Playgroud)
如果您希望这样做,您可以获取对象的引用.或者忽略它,或者其他什么.我的直接想法是:简单,这只是梅耶的单身人士伪装.从而:
struct foo
{
//...
static bar& init() { static bar b; return b; }
};
Run Code Online (Sandbox Code Playgroud)
而已!死简单,完美.这foo是在你打电话时创建的init,你得到一个bar你可以查询统计数据,它是一个参考,所以你不是所有者,并在最后foo自动清理.
除了...
当然,它可能不是那么容易,任何曾经使用过基于范围的人都auto知道auto&如果你不想要惊喜副本就必须写.但是,唉,auto单独看起来非常无辜,我没有想到它.此外,我明确地返回一个引用,所以什么auto可能捕获但参考!
结果:复制(从什么?大概来自返回的引用?),当然有一个范围的生命周期.调用默认复制构造函数(无害,无效),最终复制超出范围,上下文在操作中释放,东西停止工作.在程序结束时,再次调用析构函数.Kaboooom.嗯,那是怎么发生的.
显而易见的(好吧,在第一秒中不那么明显!)解决方案是写:
auto& blah = foo::init();
Run Code Online (Sandbox Code Playgroud)
这工作,并且工作正常.问题解决了,除了......除了它不漂亮,人们可能会意外地像我一样做错了.我们不能不需要额外的&符号吗?
它可能也可以返回a shared_ptr,但这将涉及不必要的动态内存分配,更糟糕的是,它在我的感知中是"错误的".您不共享所有权,只允许您查看其他人拥有的内容.原始指针?正确的语义,但......呃.
通过删除复制构造函数,我可以防止无辜的用户进入忘记和陷阱(这将导致编译器错误).
然而,这仍然不如我想要的那么漂亮.必须有一种方法可以将"返回值作为参考"与编译器进行通信?有点像return std::as_reference(b);?
我曾经想过一些涉及"移动"对象而没有真正移动它的骗局技巧,但不仅编译器几乎肯定不会让你移动静态本地,但如果你设法做到这一点,你要么改变了所有权,或者使用"虚假移动"移动构造函数再次调用析构函数两次.所以这不是解决方案.
有更好,更漂亮的方式,还是我只需要和写作一起生活auto&?
像return std :: as_reference(b);?
你的意思是std::ref?这将返回std::reference_wrapper<T>您提供的值.
static std::reference_wrapper<bar> init() { static bar b; return std::ref(b); }
Run Code Online (Sandbox Code Playgroud)
当然,auto将推断返回的类型reference_wrapper<T>而不是T&.虽然reference_wrapper<T>有隐含的operatorT&,但这并不意味着用户可以像引用一样使用它.要访问成员,他们必须使用->或.get().
尽管如此,我相信你的想法是错误的.其原因是,auto和auto&有东西,每一个C++程序员需要学习如何应对.人们不会让他们的迭代器类型返回reference_wrappers而不是T&.人们通常不会reference_wrapper以这种方式使用.
因此,即使您像这样包装所有接口,用户仍然必须最终知道何时使用auto&.实际上,除了特定的API之外,用户还没有获得任何安全性.
强制用户通过引用捕获是一个三步过程.
首先,使返回的东西不可复制:
struct bar {
bar() = default;
bar(bar const&) = delete;
bar& operator=(bar const&) = delete;
};
Run Code Online (Sandbox Code Playgroud)
然后创建一个小的passthrough函数,可以可靠地提供引用:
namespace notstd
{
template<class T>
decltype(auto) as_reference(T& t) { return t; }
}
Run Code Online (Sandbox Code Playgroud)
然后编写静态init()函数,返回decltype(auto):
static decltype(auto) init()
{
static bar b;
return notstd::as_reference(b);
}
Run Code Online (Sandbox Code Playgroud)
完整演示:
namespace notstd
{
template<class T>
decltype(auto) as_reference(T& t) { return t; }
}
struct bar {
bar() = default;
bar(bar const&) = delete;
bar& operator=(bar const&) = delete;
};
struct foo
{
//...
static decltype(auto) init()
{
static bar b;
return notstd::as_reference(b);
}
};
int main()
{
auto& b = foo::init();
// won't compile == safe
// auto b2 = foo::init();
}
Run Code Online (Sandbox Code Playgroud)
Skypjack正确地指出,init()可以正确编写而不用notstd::as_reference():
static decltype(auto) init()
{
static bar b;
return (b);
}
Run Code Online (Sandbox Code Playgroud)
返回周围的括号(b)强制编译器返回引用.
我对这种方法的问题在于,c ++开发人员经常会对此有所了解,因此很少有经验丰富的代码维护者可能会错过它.
我的感觉是return notstd::as_reference(b);明确地表达了对代码维护者的意图,就像std::move()那样.