Compiler hint to return link by making "auto" behave

(possibly related to How to implement the C ++ method, which creates a new object, and returns a link to it that has something else, but, by the way, contains almost exactly the same code)

I would like to return a link to a static local element from a static function. I can make it work, of course, but it's less cute than I would like.

Can this be improved?

Background

I have several classes that do nothing but receive or initialize a resource in a well-defined form and reliably, and free it. They donโ€™t even need to know a lot about the resource itself, but the user can still request some information in some way.
This, of course, is trivial:

struct foo { foo() { /* acquire */ } ~foo(){ /* release */ } }; int main() { foo foo_object; // do stuff } 

Trivial Alternatively, this will also work well:

 #include <scopeguard.h> int main { auto g = make_guard([](){ /* blah */}, [](){ /* un-blah */ }); } 

Except for now, requesting stuff is a little more complicated, and it's less cute than I like. If you prefer Stroustrup rather than Alexandrescu, you can enable GSL instead and use some mix including final_act . Whatever.

Ideally, I would like to write something like:

 int main() { auto blah = foo::init(); } 

If you return a link to an object you can request if you want to do this. Or ignore it or something else. I immediately thought: โ€œIt is easy that only Mayer Singleton is hiding. Thus:

 struct foo { //... static bar& init() { static bar b; return b; } }; 

What is it! The dead are simple and perfect. foo is created when you call init , you return a bar , which you can request for statistics, and this is a link so that you are not the owner, and foo automatically cleared at the end.

Besides...

Problem

Of course, this cannot be so simple, and anyone who has ever used a range based on auto knows that you need to write auto& if you don't want surprises. But alas, auto looked so completely innocent that I did not think about it. In addition, I explicitly return the link, so auto might possibly capture, but the link!

Result: a copy is made (from what? Presumably from the returned link?), Which, of course, has a limited lifespan. The instance constructor is called by default (harmless, does nothing), eventually the copy goes out of scope, and contexts go out in the middle of the operation, the material stops working. At the end of the program, the destructor is called again. Kaboooom. Yes, how did this happen.

The obvious (well, not so obvious in the first second!) Solution is to write:

 auto& blah = foo::init(); 

It works and works great. The problem is solved, except ... except that it is ugly, and people can accidentally do it wrong, like me. Can't we do it without requiring an extra ampersand?

It may also work to return shared_ptr , but it will require unnecessary allocation of dynamic memory, and even worse, it would be "wrong" in my perception. You do not share ownership, you are simply allowed to look at what owns someone else. Raw pointer? Correct semantics, but ... ugh.

By deleting the copy constructor, I can prevent innocent users from starting in zabr. trap (this will result in a compiler error).

This, however, is still less beautiful than we would like. There must be a way of linking "This return value should be used as a reference" to the compiler? Something like return std::as_reference(b); ?

I thought of some reason related to the "moving" of the object without moving it, but not only the compiler will almost certainly not allow you to move the static local file at all, but if you manage to do this, you either have changed ownership, or using constructor move "fake move" call the destructor twice again. So no solution.

Is there a better, more beautiful way, or do I just need to live with an auto& record?

+8
c ++ c ++ 14
source share
5 answers

Something like return std :: as_reference (b) ;?

Do you mean std::ref ? This returns std::reference_wrapper<T> value you provide.

 static std::reference_wrapper<bar> init() { static bar b; return std::ref(b); } 

Of course, auto prints the return type reference_wrapper<T> , not T& . Although reference_wrapper<T> has an implicit operatorT& , this does not mean that the user can use it exactly as a link. To access members, they must use -> or .get() .

However, all of the above, I believe that your thinking is wrong. The reason is that auto and auto& are something that every C ++ programmer needs to know how to deal with. People are not going to create their types of reference_wrapper iterators instead of T& . Usually people usually do not use reference_wrapper .

That way, even if you port all your interfaces like this, the user still needs to know when to use auto& . So, really, the user has not received any security outside of your specific APIs.

+5
source share

Forcing a user to write by reference is a three-step process.

First, make the returned thing not copied:

 struct bar { bar() = default; bar(bar const&) = delete; bar& operator=(bar const&) = delete; }; 

then create a small forwarding function that reliably passes links:

 namespace notstd { template<class T> decltype(auto) as_reference(T& t) { return t; } } 

Then write your static init () function, returning decltype (auto):

 static decltype(auto) init() { static bar b; return notstd::as_reference(b); } 

Full demo:

 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(); } 

Skypjack correctly noted that init() can be written in the same way as without notstd::as_reference() :

 static decltype(auto) init() { static bar b; return (b); } 

The braces around return (b) force the compiler to return the link.

My problem with this approach is that C ++ developers are often surprised to find out about this, so it could easily be missed by a less experienced code developer.

I feel like return notstd::as_reference(b); explicitly expresses an intention to maintain code, as std::move() does.

+5
source share

If you want to use singleton, use it correctly

 class Singleton { public: static Singleton& getInstance() { static Singleton instance; return instance; } Singleton(const Singleton&) = delete; Singleton& operator =(const Singleton&) = delete; private: Singleton() { /*acquire*/ } ~Singleton() { /*Release*/ } }; 

So you cannot create a copy

 auto instance = Singleton::getInstance(); // Fail 

whereras you can use an instance.

 auto& instance = Singleton::getInstance(); // ok. 

But if you want to have RAII instead of singleton, you can do

 struct Foo { Foo() { std::cout << "acquire\n"; } ~Foo(){ std::cout << "release\n"; } Foo(const Foo&) = delete; Foo& operator =(const Foo&) = delete; static Foo init() { return {}; } }; 

Using

 auto&& foo = Foo::init(); // ok. 

And the copy is still banned

 auto foo = Foo::init(); // Fail. 
+1
source share

The best, most idiomatic, readable, unsurprising thing to do is =delete the copy constructor and copy assignment operator and just return the link like everyone else.

But seeing how you raised smart pointers ...

The shared_ptr return may also work, but it will require unnecessary allocation of dynamic memory, and even worse, it would be โ€œwrongโ€ in my perception. You do not share ownership, you are simply allowed to look at what owns someone else. Raw pointer? Correct semantics, but ... ugh.

An unprocessed pointer would be perfectly acceptable here. If you do not like this, you have a number of options, following the "thoughts".

You can use shared_ptr without dynamic memory with user deletion:

 struct foo { static shared_ptr<bar> init() { static bar b; return { &b, []()noexcept{} }; } } 

Although the caller does not "share" ownership, he does not understand what ownership even means that the debiter is not an operator.

You can use weak_ptr containing a reference to an object controlled by shared_ptr :

 struct foo { static weak_ptr<bar> init() { static bar b; return { &b, []()noexcept{} }; } } 

But given the no_op shared_ptr destructor, this is no different from the previous example and just imposes an unnecessary .lock() call on the user.

You can use unique_ptr without dynamic memory with user deletion:

 struct noop_deleter { void operator()() const noexcept {} }; template <typename T> using singleton_ptr = std::unique_ptr<T, noop_deleter>; struct foo { static singleton_ptr<bar> init() { static bar b; return { &b, {} }; } } 

It makes sense not to need to manage pointless reference counting, but again the semantic meaning is not perfect: the caller does not imply a unique property, no matter what it actually means.

In the TS v2 core libraries, you can use observer_ptr , which is just a raw pointer that expresses an intention to be poor:

 struct foo { static auto init() { static bar b; return experimental::observer_ptr{&b}; } } 

If you do not like any of these options, you can, of course, define your own type of smart pointer.

In the future standard, you can define a โ€œsmart linkโ€ that works like reference_wrapper without .get() using an overloaded operator. .

+1
source share

Someone will make a typo in one day, and then we may or may not notice it in many codes, although we all know that we should use auto & instead of automatic.

The most convenient, but very dangerous solution is to use a derived class, because it violates the strict anti-aliasing rule.

 struct foo { private: //Make these ref wrapper private so that user can't use it directly. template<class T> class Pref : public T { private: //Make ctor private so that we can issue a compiler error when //someone typo an auto. Pref(const Pref&) = default; Pref(Pref&&) = default; Pref& operator=(const Pref&) = default; Pref& operator=(Pref&&) = default; }; public: static Pref<bar>& init() { static bar b; return static_cast<Pref<bar>&>(b); } ///....use Pref<bar>& as well as bar&. }; 
0
source share

All Articles