[CppCon 2018]Herb Sutter: Towards a simpler and more powerful C ++
3r3-31.
3r3393930. 3r3393930. 3r3393930.
3r33812. In his speech at CppCon 201? Herb Sutter presented his work in two directions to the public. First, it is
control lifetime
variables (Lifetime), which will detect the entire classes of bugs at the compilation stage. Secondly, this is an updated proposal for
metaclasses
which will allow to avoid duplication of the code, once describing the behavior of the category of classes and then connecting it to specific classes in one line.
3r318. 3r33825.
Preface: more = easier ?! 3r3405.
3r33812. C ++ charges that the standard is meaningless and mercilessly growing are heard. But even the most ardent conservatives will not argue with the fact that such new constructions as range-for (cycle through the collection) and auto (at least for iterators) make the code simpler. It is possible to develop exemplary criteria with which (at least one, ideally all) new extensions of the language should satisfy in order to simplify the code in practice: 3r3826.
3r330.
Reduce, simplify code, remove duplicate code (range-for, auto, lambda, Metaclasses)
Make safe code easier to write, prevent mistakes and special cases (smart pointers, Lifetimes) 3r33918.
Completely replace older, less functional features (typedef → using)
3r33812. Herb Sutter identifies a “modern C ++” - a subset of features that conform to modern coding standards (like 3r3453. C ++ Core Guidelines
), And considers the complete standard as a “compatibility mode” that everyone does not need to know. Accordingly, if "modern C ++" does not grow, then everything is fine.
3r350. Checks the lifetime of variables (Lifetime) 3r3405.
3r33812. A new group of Lifetime checks is now available in Core Guidelines Checker for Clang and Visual C ++. The goal is not to achieve absolute rigor and accuracy, as in Rust, but to perform simple and quick checks within individual functions.
The basic principles of validation 3r3783.
3r33812. From the point of view of the analysis of the lifetime, the types are divided into 3 categories:
3r33737.
The value is what any Pointer
may indicate.
Pointer (pointer) - refers to the value, but does not control its lifetime. Can be dangling (dangling pointer). Examples: 3r3752. T * 3r3753. , 3r3752. T & , iterators, std :: observer_ptr 3r3753. , 3r3752. std :: string_view
, 3r3752. gsl :: span 3r3753. 3r33918.
The owner (owner) - manages the lifetime values. Usually can remove his value ahead of time. Examples: 3r3752. std :: unique_ptr 3r3753. , 3r3752. std :: shared_ptr 3r3753. , 3r3752. std :: vector 3r3753. , 3r3752. std :: string , 3r3752. gsl :: owner 3r3753. 3r33918.
3r33812. The pointer may be in one of the following states:
3r33737.
Point to the value stored on the stack
Point to the Value contained "inside" of some Owner
Be empty (null)
To be invalid
Pointers and Values
3r33812. For each Pointer 3r3191. tracked
3r3734. 3r33737. string_view s; //pset (s) = {null}
{
char a[100];
s = a; //pset (s) = {a}
cout s[0]; //OK
} //pset (s) = {invalid} 3r3393934. cout s[0]; //ERROR: invalid ∈ pset (s) 3r33748.
3r33812. With annotations you can configure what actions will be considered as referring to the operation value. Default: 3r3752. * , 3r3752. -> 3r3753. , 3r3752.[]3r3753. , 3r3752. begin () , 3r3752. end () .
3r33812. I pay attention that the warning is issued only at the moment access to invalid pointer. If the Value is deleted, but no one will ever turn to this Index, then everything is fine.
3r3185. Pointers and Owners
3r33812. If the pointer is 3r3191. indicates the Value contained within the Owner 3r3194. then this is denoted 3r3197. .
3r33812. The methods and functions that host Owners are divided into: 3r3826.
3r33737.
Owner Value Access operations. Default: 3r3752. * , 3r3752. -> 3r3753. , 3r3752.[]3r3753. , 3r3752. begin () , 3r3752. end () 3r33918.
Access operations to the Owner itself, invalid pointer, like 3r3752. v.clear () . By default, these are all other non-const operations
Access operations to the Owner itself, not invalid pointer pointers, like 3r3752. v.empty () . By default, these are all const operations
3r33812. The old content of the owner is declared
3r33812. These rules are enough to detect many typical bugs in C ++ code:
3r3734. 3r33737. string_view s; //pset (s) = {null}
string name = "foo";
s = name; //pset (s) = {name '}
cout s[0]; //OK
name = "bar"; //pset (s) = {invalid}
cout s[0]; //ERROR 3r33748.
3r3734. 3r33737. vector v = get_ints ();
int * p = & v[5]; //pset (p) = {v '}
v.push_back (42); //pset (p) = {invalid}
cout * p; //ERROR 3r33748.
3r3734. 3r33737. std :: string_view s = "foo" s;
cout s[0]; //ERROR
//Decryption: save the pointer to the contents of the temporary object
std :: string_view s = "foo" s //pset (s) = {"foo" s'}
; //pset (s) = {invalid} 3r33748.
3r3734. 3r33737. vector v = get_ints ();
for (auto i = v.begin (); i! = v.end (); ++ i) {//pset (i) = {v '}
if (* i == 2) {
v.erase (i); //pset (i) = {invalid}
} //pset (i) = {v ', invalid}
} //ERROR: ++ i
for (auto i = v.begin (); i! = v.end ();) {
if (* i == 2) i = v.erase (i); //OK
else ++ i;
} 3r33748.
3r3734. 3r33737. std :: optional (* get_data ())) //OK
cout value; 3r3753. 3r33748.
Tracking the lifetime of the function parameters parameters
3r33812. When we start dealing with functions in C ++ that return Pointers, we can only guess about the relationship between the lifetime of the parameters and the return value. If the function accepts and returns Pointers to the same type, then it is assumed that the function “gets” the return value from one of the input parameters: 3r3826.
3r3734. 3r33737. auto f (int * p, int * q) -> int *; //pset (ret) = {p ', q'}
auto g (std :: string & s) -> char *; //pset (ret) = {s'} 3r33748.
3r33812. Suspicious functions are easily detected that take the result from nowhere:
3r3734. 3r33737. std :: reference_wrapper get_data () {//odd type of function
int i = 3;
return {i}; //pset (ret) = {i '}
} //pset (ret) = {invalid} 3r33748.
3r33812. As in the parameters 3r3752. const T & you can pass a temporary value, they are not taken into account, except in cases where the result is nowhere else to take:
3r3734. 3r33737. template 3r3607.
const T & min (const T & x, const T & y); //pset (ret) = {x ', y'}
//Returns a pointer to the const T & -parameter
//With this function, you need to be extremely careful
auto x = 1? y = 2;
auto & bad = min (x, y + 1); //pset (bad) = {x, temp}
//pset (bad) = {x, invalid}
cout bad; //ERROR 3r33748.
3r3734. 3r33737. using K = std :: string;
using V = std :: string;
const v & find_or_default (const std :: map & m, const K & key, const V & def);
//pset (ret) = {m ', key', def '}
std :: map map;
K key = "foo";
const V & s = find_or_default (map, key, "none");
//pset (s) = {map ', key', temp} ⇒ pset (s) = {map ', key', invalid} 3r3393934. cout s; //ERROR 3r33748.
3r33812. It is also believed that if a function takes a pointer (instead of a link), then it can be nullptr, and this pointer cannot be used before comparing with nullptr.
3r33382. Conclusion on lifetime control
3r33812. I repeat that Lifetime is not yet a suggestion for the C ++ standard, but a bold attempt to introduce lifetime checks in C ++, where, unlike Rust, for example, there have never been corresponding annotations. At first, there will be a lot of false positives, but over time, heuristics will improve.
3r33333. Questions from room
3r33812. Do Lifetime group checks provide a mathematically accurate guarantee that there are no dangling pointers? 3r3808.
3r33812. Theoretically, it would be possible (in the new code) to hang a bunch of annotations on classes and functions, and in return, the compiler would give such guarantees. But these checks were developed following the 80:20 principle, that is, you can catch most of the errors using a small number of rules and applying a minimum of annotations.
3r3404. Metaclasses
3r33812. Last year, Herb Sutter first came up with its metaclass project (3r3409. See here 3r32525.). Since then, the current proposed syntax has changed.
3r33812. For starters, the metaclass usage syntax has changed:
3r3734. 3r33737. //It was
interface Shape {
int area () const;
void scale_by (double factor);
};
//It was
class (interface) Shape {} 3r33748.
3r33812. It has become longer, but now there is a natural syntax for using several metaclasses at once:
class (meta? meta2)
. Description of the metaclass
3r33812. Previously, metaclass was a set of rules for modifying a class. Now the metaclass is the constexpr-function, which takes the old class (declared in the code) as input and creates a new one.
3r33812. Namely, the function takes one parameter - the meta information about the old class (the type of the parameter depends on the implementation), creates an elementclass awards (fragments), after which they are added to the inside of the body of a new class using instruction 3r3752. __generate .
3r33812. Fragments can be generated using
constructs. __fragment
, 3r3752. __inject 3r3753. , 3r3752. idexpr () . The speaker chose not to focus on their purpose, as this part will change before it is presented to the standardization committee. The names themselves are guaranteed to be changed, double underscores have been added specifically to clarify this. The emphasis in the report was on examples, which go further. 3rr3461. interface
3r3734. 3r33737. template 3r3607.
constexpr void interface (T source) {//source describes the source class
//Initially, the body of the target class is empty. Here we add
there. //destructor ~ X, where X is the name of the target class.
__generate __fragment struct X {3r3393934. virtual ~ X noexcept {}
};
//Unlike static_assert, compiler.require can use
//value of constexpr-function parameter.
//Do not declare variables in the source class.
compiler.require (source.variables (). empty (), 3r-3934. "interfaces may not contain data members");
//member_functions () probably returns tuple <…> so we need for
for (auto f: source.member_functions ()) {
//Check that the function is not a copy /assignment constructor
compiler.require (! f.is_copy () &&! f.is_move (),
"consider a virtual clone ()");
//Make the public function by default
if (! f.has_default_access ())
f.make_public (); //(1)
//Check that the function was not declared protected /private
compiler.require (f.is_public (), "interface functions must be public");
//Make the function purely virtual
f.make_pure_virtual (); //(2)
//Add the function f to the body of the new class
__generate f;
}
} 3r33748.
3r33812. You might think that on lines (1) and (2) we modify the original class, but no. Please note that we iterate over the functions of the original class with copying, modify these functions, and then insert them into the new class.
3r33812. Application of the metaclass:
3r3734. 3r33737. class (interface) Shape {
int area () const;
void scale_by (double factor);
};
//Converted to:
class Shape {
public: virtual ~ Shape noexcept {}
public: virtual int area () const = 0;
public: virtual void scale_by (double factor) = 0;
}; 3r3753. 3r33748.
3r33532. Debugging the mutex
3r33812. Suppose we have non-thread safe data protected by a mutex. You can make debugging easier if you check in the debug build to see if the current process is locked in this mutex. For this, a simple TestableMutex class was written:
3r3734. 3r33737. class TestableMutex {
public:
void lock () {m.lock (); id = std :: this_thread :: get_id ();}
void unlock () {id = std :: thread :: id {}; m.unlock ();}
bool is_held () {return id == std :: this_thread :: get_id ();}
private:
std :: mutex m;
std :: atomic id;
}; 3r3753. 3r33748.
3r33812. Further, in our class MyData I would like every public field like 3r3826.
3r3734. 3r33737. vector v; 3r3753. 3r33748.
3r33812. Replace with field + getter:
3r3734. 3r33737. private:
vector v_;
public:
vector & v () {assert (m_.is_held ()); return v_;} 3r33748.
3r33812. For functions, you can also carry out similar transformations
3r33812. Such tasks are solved using macros and code generation. Herb Sutter declared war on macros: they are insecure, ignore semantics, namespaces, etc. What the solution looks like on metaclasses:
3r3734. 3r33737. constexpr void guarded_with_mutex () {
__generate __fragment class {
TestableMutex m_;
//lock, unlock
}
}
template
constexpr void guarded_member (T type, U name) {
auto field = ;
__generate field;
auto getter = ;
__generate getter;
}
template 3r3607.
constexpr void guarded (T source) {3r3393934. guarded_with_mutex ();
for (auto o: source.member_variables ()) {
guarded_member (o.type (), o.name ());
}
} 3r33748.
3r33812. How to use it:
3r3734. 3r33737. class (guarded) MyData {
vector v;
Widget * w;
};
MyData & x = findData ("foo");
x.v (). clear (); //assertion failed: m_.is_held () 3r33748.
actor
3r33812. Well, let us protect some object with a mutex, now everything is thread-safe, there are no complaints about correctness. But if the object can often be accessed in parallel by multiple threads, then the mutex will be overloaded, and there will be a large overhead on its capture.
3r33812. The fundamental solution to the problem of buggy mutexes is the concept of actors, when an object has a queue of requests, all references to an object are put in a queue and executed one after another in a special thread.
3r33812. Let the class Active contain an implementation of all of this — essentially, a single-thread thread pool /executor. Well, metaclasses will help get rid of duplicate code and put all operations in a queue: 3r3826.
3r3734. 3r33737. class (active) ImageFilter {
public:
ImageFilter (std :: function W): work (std :: move (w)) {}
void apply (Buffer * b) {work (b);}
private:
std :: function work;
}
//Converted to:
class ImageFilter {
public:
ImageFilter (std :: function W): work (std :: move (w)) {}
void apply (Buffer * b) {3r3393934. a.send ([=]{work (b);}). join ();
}
private:
std :: function work;
Active a; //must be the last to start deleting before work
} 3r33748.
3r3734. 3r33737. class (active) log {
std :: fstream f;
public:
void info () {f ;}
}; 3r3753. 3r33748.
property
3r33812. There are properties in almost all modern programming languages, and those who only did not implement on the basis of C ++: Qt, C ++ /CLI, all sorts of ugly macros. However, they will never be added to the C ++ standard, since by themselves they are considered too narrow features, and there has always been hope that a proposal will implement them as a special case. Well, they can be implemented on metaclasses!
3r3734. 3r33737. //Writing
class X {
public:
class (property ) WidthClass {} width;
};
//Get
class X {
public:
class WidthClass {
int value;
int get () const;
void set (const int & v);
void set (int && v);
public:
WidthClass ();
WidthClass (const int & v);
WidthClass & operator = (const int & v);
operator int () const;
//Free support move!
WidthClass (int && v);
WidthClass & operator = (int && v);
} width;
}; 3r3753. 3r33748.
3r33812. You can set your own getter and setter:
3r3734. 3r33737. class Date {
public:
class (property ) MonthClass {
int month;
auto get () {return month;}
void set (int m) {assert (m> 0 && m < 13); month = m; }
} month;
};
Date date;
date.month = 15; //assertion failed
3r33812. Ideally, I want to write
property int month {}
, but such an implementation will replace the zoo of C ++ extensions, which invents properties.3r3757. Conclusion on metaclasses
3r33812. Metaclasses are a great new feature for an already complex language. Is it worth it? Here are some of their benefits:
3r33737.
Allow programmers to more clearly express their intentions (I want to write actor)
Reduce duplication of code and simplify the development and maintenance of code that follows certain patterns 3r-3918.
Eliminate some groups of common mistakes (it will be enough to take care of all the intricacies once)
Will get rid of macros? (Herb Sutter is very belligerent)
3r33782. Questions from room
3r33812.
How to debug metaclasses? 3r3808.
3r33812. At least for Clang there is an intrinsic-function, which, if called, prints the actual contents of the class at compile time, that is, what is obtained after applying all the metaclasses.
3r33812.
It used to be said about the possibility of declaring non-members like swap and hash in metaclasses. Where did she go? 3r3808.
3r33812. The syntax will be refined.
3r33812.
Why do we need metaclasses if concepts have already been adopted for standardization (Concepts)? 3r3808.
3r33812. These are different things. Metaclasses are needed to define parts of a class, and concepts check if a class matches a certain pattern using class examples. In fact, metaclasses and concepts are perfectly combined. For example, you can define an iterator concept and a typical iterator metaclass, which defines some redundant operations through the rest.
3r3393930.
3r33817. ! function (e) {function t (t, n) {if (! (n in e)) {for (var r, a = e.document, i = a.scripts, o = i.length; o-- ;) if (-1! == i[o].src.indexOf (t)) {r = i[o]; break} if (! r) {r = a.createElement ("script"), r.type = "text /jаvascript", r.async =! ? r.defer =! ? r.src = t, r.charset = "UTF-8"; var d = function () {var e = a.getElementsByTagName ("script")[0]; e.parentNode.insertBefore (r, e)}; "[object Opera]" == e.opera? a.addEventListener? a.addEventListener ("DOMContentLoaded", d,! 1): e.attachEvent ("onload", d ): d ()}}} t ("//mediator.mail.ru/script/2820404/"""_mediator") () (); 3r33818.
3r3393930.
Only registered users can participate in the survey. 3r33824. Log in 3r33825. , you are welcome.
3r33831.
3r33833. Do I need metaclasses in the C ++ standard? 3r33939.
3r3393930.
3r33838.
3r33841.
3r33843.
3r33845.
3r33848.
3r3r6906.
3r3908.
3r33912. Yes, I need
3r33915.
3r33918.
3r3r6906.
3r3908.
3r33912. Yes, because why not
3r33915.
3r33918.
3r3r6906.
3r3908.
3r33912. No, macros and code generators are easier
3r33915.
3r33918.
3r3r6906.
3r3908.
3r33439. 3r33912. No, stop inflating the standard with every heresy! 3r33939.
3r33915.
3r33918.
3r3r6906.
3r3908.
3r33912. What are metaclasses? 3r33939.
3r33915.
3r33918.
3r33939.
3r3393930.
3r33939. No one has voted yet. There are no abstentions. 3r3393930.
3r3393930.
It may be interesting