提示编译器返回使'auto'行为的引用

Dam*_*mon 8 c++ c++14

(可能与如何实现创建新对象的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&

Nic*_*las 5

像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().

尽管如此,我相信你的想法是错误的.其原因是,autoauto&有东西,每一个C++程序员需要学习如何应对.人们不会让他们的迭代器类型返回reference_wrappers而不是T&.人们通常不会reference_wrapper以这种方式使用.

因此,即使您像这样包装所有接口,用户仍然必须最终知道何时使用auto&.实际上,除了特定的API之外,用户还没有获得任何安全性.

  • +1因为这个 - _原因是auto和auto都是每个C++程序员都需要学习如何处理的东西.如此真实. (2认同)

Ric*_*ges 5

强制用户通过引用捕获是一个三步过程.

首先,使返回的东西不可复制:

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()那样.

  • 仅删除复制构造函数有什么改进?如果你意外地按价值捕获,那就已经无法编译了. (2认同)