In this post I will talk about the new and (I think) relatively little-known C ++ feature -
reference-qualified member functions . I'll tell you about the rules for overloading such functions, and also, as an example of use, I will tell you how to use
ref-qualifie d functions to try to improve the resource management scheme implemented using another C ++ -
RAII idiom .
Introduction
So, recently in C ++, it has become possible to qualify member functions as a link (at least outwardly it looks like a link). These qualifications can be
lvalue ,
rvalue links, can be combined with
const qualifications.
class some_type { void foo() & ; void foo() && ; void foo() const & ; void foo() const && ; };
Why do you need it?
Strictly speaking, officially this feature is called a little differently, namely
“ref-qualifiers for * this” or
“rvalue references for * this” . But it seems to me that the name is a bit confusing, since it may seem that the object changes its type when calling functions with different qualifications. And in fact, the type of
* this never changes. So what's the trick? And the trick is that thanks to these qualifiers it becomes possible to overload member functions by context (rvalue, lvalue, etc) in which the object is used.
')
int main() { some_type t; t.foo();
How it works?
To begin with, C ++ already has a mechanism for resolving overload between member functions and free functions. Why do you need it, you ask, and so it is possible to understand whether a free function or a class method is called at least externally, by syntax, in one case
obj.f () , in the other just
f () ? The fact is that when it comes to operator overloading, there can be no difference in syntax. for example
struct some_type { bool operator == (int) const; }; bool operator == (const some_type& l, long r); void g() { some_type t; int i = 42; t == i;
To resolve such an overload, the compiler represented a member function as a free function with an additional parameter - a reference to an object that has a function call and then allowed an overload among all free functions. So in order to implement the innovation, it was necessary to “tweak” already existing behavior a little, namely, to create different signatures of candidate overloads for variously qualified member functions.
I will say a few words about how this mechanism works, because it is not always obvious which function is the best candidate for overloading in one way or another. Consider again the code from the first example.
class some_type { void foo() & ;
There are 3 candidates for this call: 2, 3 and 4. There are special rules between them that allow them to appear on the paper rather verbose and complex, but the essence of which is that the function most appropriate for the type is chosen.
I will try to retell the chain of reasoning on the conclusion of the candidate, as I imagine it. In this example, the expression
some_type () is rvalue . Potentially, functions 2, 3, or 4 can be called. But the
rvalue reference qualified functions more “correspond” to the type of the original expression
(rvalue) than
const & . Options 2 and 4 remain. In the fourth version, for full compliance, it is necessary to make an additional action on the initial type - add
const , whereas in the 2nd version no additional actions are required. Therefore, in the end option 2 will be selected.
How to use?
It is obviously convenient to use this innovation in those cases when the behavior of the object should differ from the contexts in which it is used. For example, we can make it safer to use a pointer to a stored resource when using
RAII. class file_wrapper { public:
In this example,
operator FILE * () represents a huge hole in the safe use of a file wrapper.
Imagine this context of use:
FILE* f = file_wrapper("some_file.txt", "r");
Now we have the opportunity to make this, in fact very convenient, function more (but not completely) safe.
operator FILE* () & {return held_;}
You can look at
RAII from a slightly different angle. Since we can now “understand” that we are called in different contexts, let's just transfer ownership of the resource instead of copying in cases where our object will no longer be used.
template <typename T> class some_type { public: operator std::unique_ptr<T>() const & { return std::unique_ptr<T>(new T(*held_));