[Reading notes] Effective Modern Cpp (b)

Rvalue references, move semantics and perfect forwarding

23 std :: move and std :: forward just a cast

  • std :: move retained cv defined symbol, but it will cause the incoming rvalue perform the copy operation. So if you want to move the value of std :: move, pass the value type can not be const
  • C ++ 11 introduces a reference to the value of the right, but left the original template can not be forwarded value, so the introduction of std :: forward
void f(int&) { std::cout << 1; }
void f(const int&) { std::cout << 2; }
void f(int&&) { std::cout << 3; }

// 用多个重载转发给对应版本比较繁琐
void g(int& x)
{
    f(x);
}

void g(const int& x)
{
    f(x);
}

void g(int&& x)
{
    f(std::move(x));
}

// 同样可以用一个模板来替代上述功能
template<typename T>
void h(T&& x)
{
    f(std::forward<T>(x)); // 注意std::forward的模板参数是T
}

int main()
{
    int a = 1;
    const int b = 1;

    g(a); h(a); // 11
    g(b); h(b); // 22
    g(std::move(a)); h(std::move(a)); // 33
    g(1); h(1); // 33
}
  • std :: forward may be substituted std :: move, but the latter is more clear and simple
h(std::forward<int>(a)); // 3
h(std::move(a)); // 3

24 forward references cited differences with the right value

  • With the right reference value is not necessarily a sign rvalue references, citing uncertain called this type of forward references
  • Forward references must be in strict accordance with T && form of type inference involving
template<typename T>
void f(std::vector<T>&&) {} // 右值引用而非转发引用

std::vector<int> v;
f(v); // 错误

template<typename T>
void g(const T&&) {} // 右值引用而非转发引用

int i = 1;
g(i); // 错误 
  • auto && are forward references because some involve the type inference
  • lambda can also use perfect forward.

25 using std :: move to the right reference values, use std :: forward forwarding references

  • Rvalue reference only bound to the movable object on, so you should use std :: move; forward is the right reference value when the reference value is initialized with the right, with std :: forward
class A{
public:
    //右值引用
    A(A&& rhs) : s(std::move(rhs.s)),p(std::move(rhs.p)){}
    template<typename T>
    void f(T&& x){
        s = std::forward<T>(x); //转发引用
    }
private:
    std::string s;
    std::shared_ptr<int> p;     
  • If you only want to move into the constructor to ensure that the right value is not thrown by std :: move_ if_noexcept replace std :: move
  • If the returned object is passed rvalue reference or forward reference, use std :: move or when returning std :: forward conversion.
  • Local variables are created directly on the return value of the allocated memory, in order to avoid copying. std :: move does not meet the requirements of the RVO.

26 Avoid using function overloading forward references

  • If the function parameter accepts lvalue reference, the execution of the right values ​​still an incoming copy
void f(const std::string& s){
    v.emplace_back(s);
}
//传入右值,执行的仍然是拷贝
f(std::string (“hi”));
f("hi"); 

//但是让函数接受转发引用就可以解决问题
void f(T&& s){
    v.emplace_back(std::forward<T>(s));
}
//现在就是移动操作
f(std::string(“hi”));
f("hi");    
  • Forward references can match almost any type. But if the overload will cause problems.

27 heavy-duty forwarding alternatives cited

  • Label assignment: the introduction of additional parameters to break a forward referenced universal matching
template<typename T>
//额外引入参数
void g(T&& s, std::false_type){
    v.emplace_back(std::forward<T>(s));
}

std::string makeString(int n){
    return std::string("hi");
}

void g(int n, std::true_type){
    v.emplace_back(makeString(n));
}

template<typename T>
void f(T&& s){
    g(std::forward<T>(s), std::is_integral<std::remove_reference_t<T>>());
}

unsigned i = 1;
f(i); // OK:调用int版本
  • Using std :: enable_if disable template under certain conditions
    • Tag is used in the constructor convenient dispatch, you can use std :: enable_if
    • When a derived class calls the base class constructor, the derived class and the base class are different types, the template is not disabled, it is also necessary to use std :: is_base _of
    • To facilitate debugging, you can use static_assert default error message.

Reference 28 fold

  • Context appear: template instantiation, auto type inference, decltype type inference, typedef / using the alias declaration
//引用的引用非法。
int a = 1;
int& & b = a;   //错误

//当左值传给接受转发引用的模板,模板参数会推断成引用的引用
template<typename T>
void f(T&&);
int i = 1;
f(i);   //T是int&,T& &&变成引用的引用。引入折叠
  • Rule folding mechanism following references:
& + & → &
& + && → &
&& + & → &
&& + && → && 

29 moves faster than the case where the copy

  • No movement operation: to be the moving object does not provide movement operation, move request changes to the copy request
  • Faster than copying movement: although the moving object to be moved operation, copy operation but faster than
  • Mobile unavailable: This can be moved, the movement operation requires not throw exception statement but plus noexcept
  • Some scenes without the use of special move semantics. For example RVO
  • Movement is not necessarily faster than copying: array, string (15 bytes when the small string)

Perfect 30 type can not be forwarded

  • Call the original function parameters and forwarding functions with the same solid, if both perform different operations, called perfect forwarding failure.
  • Braces initialization
  • As a null pointer 0 / NULL
  • Only declared but undefined static const int data member
class A {
public:
    static const int n = 1; // 仅声明
};
 
void f(int) {}
 
template<typename T>
void fwd(T&& x){
    f(std::forward<T>(x));
}
 
f(A::n); // OK:等价于f(1)
fwd(A::n); // 错误:fwd形参是转发引用,需要取址,无法链接
  • Name and function of the overloaded function template name
    • If forwarding is a function pointer, directly as a parameter to the function name, the function name will be converted to function pointers
    • But if you want to forward the name of the function corresponding to a plurality of overloaded functions can not be forwarded because the template function types can not be inferred from the individual function names
  • Bit field
    • Forward reference is a reference to check the site, but does not allow the bit field
struct A {
    int a : 1;
    int b : 1;
};
 
void f(int) {}
 
template<typename T>
void fwd(T&& x){
    f(std::forward<T>(x));
}
 
A x{};
f(x.a); // OK
fwd(x.a); // 错误
    

lambda expressions

31 capture of potential problems

  • The value of the object captured state saved only capture
  • Reference capture will remain consistent with the state of the object is captured
  • When capturing a reference, the local variable in the capture destructor call lambda, dangling references appears
  • C ++ 14 provides a generalized lambda capture
struct A {
    auto f(){
        return [i = i] { std::cout << i; };
    };
    int i = 1;
};

auto g(){
    auto p = std::make_unique<A>();
    return p->f();
}
// 或者直接写为
auto g = [p = std::make_unique<A>()]{ return p->f(); };

g()(); // 1

32 is initialized to capture the object into the closure

  • move-only type can not be copied, can only use reference capture
  • Initialization capture the support of the move-only type object moves into the lambda
auto p = std::make_unique<int>(42);
auto f = [p = std::move(p)]() {std::cout << *p; };
assert(p == nullptr);
  • Direct initialization move-only type object in the capture list
    auto f = [p = std::make_unique<int>(42)]() {std::cout << *p; };

  • If you are using lambda in C ++ 11 and analog capture initialization, need to use std :: bind
auto f = std::bind(
    [](const std::unique_ptr<int>& p) { std::cout << *p; },
    std::make_unique<int>(42));
  • bind object contains copies of all the arguments. Left value of argument: the copy constructor, the right value of argument: mobile structure
  • By default, lambda operator generated class closure () default const, member variables are
  • Lifetime bind the same objects and closures

33 to obtain the auto && parameter type std :: forward with decltype

  • For generic lambda can use perfect forward. With decltype:
    • If you pass auto && left argument values ​​x the reference types, the decltype (x) to the left of the value to the left of reference type value
    • If auto && passed to the argument is the right value, x value reference type, the decltype (x) is the reference value of the right is a right type
      auto f = [](auto&& x) { return g(std::forward<decltype(x)>(x)); };

34 with an alternative std :: bind lambda

  • Compared
auto f = [l, r] (const auto& x) { return l <= x && x <= r; };

// 用std::bind实现相同效果
using namespace std::placeholders;
// C++14
auto f = std::bind(
    std::logical_and<>(),
    std::bind(std::less_equal<>(), l, _1),
    std::bind(std::less_equal<>(), _1, r));
// C++11
auto f = std::bind(
    std::logical_and<bool>(),
    std::bind(std::less_equal<int>(), l, _1),
    std::bind(std::less_equal<int>(), _1, r));
  • You can specify values ​​lambda capture and reference capture, but there will always bind by value copies
  • lambda overloaded functions can be used normally, but can not distinguish between overloaded versions of bind
  • C ++ 14 does not need to use std :: bind, C ++ 11 there are two scenarios need:
    • Analog C ++ 11 missing mobile capture
    • operator function object () is the template, to use this function object as a parameter, using std :: bind binding to accept any type arguments

Concurrent API

35 by std :: async Alternatively std :: thread

  • Alternatively asynchronous operation function is: Create a std :: thread; Another option is to use std :: async, it returns a calculation result std :: future
int f();
std::thread t(f);
std::future<int> ft = std::async(f);
  • If the function returns a value, thread can not directly get, but with the future of async can get acquired. When abnormal, get access, thread directly terminate the program.
  • Concurrent C ++, the thread is defined:
    • is the actual hardware thread threads perform calculations, computer architecture provides one or more hardware threads per CPU core
    • software thread (OS thread or system thread) is a cross-operating system process management, and perform hardware thread scheduling thread
    • std :: thread C ++ is an object of the process, as the underlying OS thread of the handle
  • std :: async library can oversubscription issues left to resolve author
  • std :: async thread to share the burden of manual management, and provides the results of inspection asynchronous execution of the function of the way
  • The following cases still need to use std :: thread
    • Need access to the underlying API thread
    • The need for the application to optimize thread usage
    • Threading technology to achieve the standard library does not provide, such as thread pool

36 by std :: launch :: async asynchronous specified evaluation

  • std :: async start two strategies:
    • std :: launch :: async: it must asynchronously, running in different threads
    • std :: launch :: deferred: function only runs when return to future calls get / wait.
  • The default startup mode: allows asynchronous or synchronous
auto ft1 = std::async(f); // 意义同下
auto ft2 = std::async(std::launch::async | std::launch::deferred, f); 
  • std :: async default boot policy conditions to be met to create:
    • Task does not need to call to get or wait for the return value of the thread concurrent execution
    • No effect on which thread to read and write variables of thread_local
    • Or guarantee or wait for the call to get the return value, either accept the task may never execute
    • Use wait_for or wait_until code to consider the possibility of the task is delayed
  • As long as it does not meet the above, we must use std :: launch :: async
template<typename F, typename... Ts>
inline
auto // std::future<std::invoke_result_t<F, Ts...>>
reallyAsync(F&& f, Ts&&... args){
    return std::async(std::launch::async,
        std::forward<F>(f),
        std::forward<Ts>(args)...);
}

37 RALL thread management

  • Each std :: thread state objects can be merged or not merged. Unmergeable:
    • The default constructor of std :: thread
    • It has moved std :: thread
    • It has join or join a thread
  • May be combined if the destructor std :: thread object is called, the program execution will terminate
void f() {}

void g(){
    std::thread t(f); // t.joinable() == true
}

int main(){
    g(); // g运行结束时析构t,导致整个程序终止
    ...
} 
  • The destruction of a merger of std :: thread will result in termination of the program. To avoid termination of the program, as long as the threads can be combined in the destruction of the merger becomes unavailable state can use RAII techniques can achieve this
class A {
public:
    enum class DtorAction { join, detach };
    A(std::thread&& t, DtorAction a) : action(a), t(std::move(t)) {}
    ~A(){
        if (t.joinable()){
            if (action == DtorAction::join) t.join();
            else t.detach();
        }
    }
    A(A&&) = default;
    A& operator=(A&&) = default;
    std::thread& get() { return t; }
private:
    DtorAction action;
    std::thread t;
};
void f() {}

void g(){
    A t(std::thread(f), A::DtorAction::join); // 析构前使用join
}

int main(){
    g(); // g运行结束时将内部的std::thread置为join,变为不可合并状态
    // 析构不可合并的std::thread不会导致程序终止
    // 这种手法带来了隐式join和隐式detach的问题,但可以调试
    ...
}

Destructor 38 std :: future behavior of

  • Destruction std :: future sometimes expressed as an implicit join, sometimes expressed as an implicit detach, sometimes expressed as neither join nor implicit implicit detach, but it does not lead to program termination.
std::promise<int> ps;
std::future<int> ft = ps.get_future();

  • callee result is stored in a location outside: shared state

  • shared state usually represented by heap object, determines the future behavior of the destructor:
    • Using std :: launch :: async start std :: async strategy returned std :: future, the last a reference to the shared state, the destructor will remain blocked until the completion of the task execution. Essentially, such a std :: future destructor is performed once the underlying asynchronous thread running implicit join
    • All other std :: future destructor destructor simply objects. Task underlying asynchronous operation, which is equivalent to the implementation of a thread implicitly the detach . For the task was postponed, if this is the last std :: future], it means that the deferred task will not run again
  • Specific behavior occurs when the following conditions destructor:
    • shared state future reference created by the calling std :: async
    • Task start strategy is std :: launch :: async
    • The future is the last reference to the shared state
  • Only shared state that occurs when async call is likely to emerge special behavior
  • Causes normal behavior destructor
{
    std::packaged_task<int()> pt(f);
    auto ft = pt.get_future(); // ft可以正常析构
    std::thread t(std::move(pt));
    ... // t.join() 或 t.detach() 或无操作
} // 如果t不join不detach,则此处t的析构程序终止
// 如果t已经join了,则ft析构时就无需阻塞
// 如果t已经detach了,则ft析构时就无需detach
// 因此std::packaged_task生成的ft一定可以正常析构    

39 by std :: std :: Promise and future communication between the implemented time notification

  • Let a mission to inform another asynchronous task specific event occurs, an implementation method is to use a condition variable, and the other is to use std :: promise :: set_value notice std :: future :: wait
std::promise<void> p;

void f(){
    p.get_future().wait(); // 阻塞至p.set_value
    std::cout << 1;
}

int main(){
    std::thread t(f);
    p.set_value(); // 解除阻塞
    t.join();
}
//但是promise和future之间的shared state是动态分配的
//存在堆上的分配和回收成本。promise只能设置一次。
//一般用来创建暂停状态的thread
  • std :: condition_ variable :: notify_ all] can notify a plurality of tasks, which may be implemented by communication between shared_future std :: promise and [std ::
std::promise<void> p;

void f(int x){
    std::cout << x;
}

int main()
{
    std::vector<std::thread> v;
    auto sf = p.get_future().share();
    for(int i = 0; i < 10; ++i) v.emplace_back([sf, i]{ sf.wait(); f(i); });
    p.set_value();
    for(auto& x : v) x.join();
}

40 std :: atomic operation provides atomic, volatile optimize memory prohibition

  • std :: atomic atomic operation
std::atomic<int> i(0);

void f(){
    ++i; // 原子自增
    ++i; // 原子自增
}

void g(){
    std::cout << i;
}

int main(){
    std::thread t1(f);
    std::thread t2(g); // 结果只能是0或1或2
    t1.join();
    t2.join();
}
  • Common non-volatile variable is the type of atoms, the atomic operation is not guaranteed
volatile int i(0);

void f(){
    ++i; // 读改写操作,非原子操作
    ++i; // 读改写操作,非原子操作
}

void g(){
    std::cout << i;
}

int main(){
    std::thread t1(f);
    std::thread t2(g); // 存在数据竞争,值未定义
    t1.join();
    t2.join();
}
  • std :: atomic reordering can limit order to ensure consistency. not volatile.
  • volatile memory is something special to tell the compiler being processed, do not operate on the optimization of this memory.

Other minor adjustments

41 can be copied for the parameter, if the mobile and low cost will be copied consider traditional values

  • Some functions of parameter for copying itself, the values ​​should be left argument of copying is performed, the value of the argument of right movement should be performed
  • 98 in C ++, is passed by value must be configured to copy, but 11 in C ++, an incoming copy only left value, if the incoming mobile rvalue
  • The cost of overloading and templates are: the value of one copy left, right, once the value of mobile. Left value by value of one copy once moved, the value of twice the right movement. One more move by value but avoid trouble.
  • Can be copied before considering the transmission parameter value, move-only type because only one type of function processing rvalue
  • Only when the mobile is low cost, extra time was worth considering moving, and therefore should only pass the parameter values ​​will be copied

Alternatively insert 42 manipulation operation emplace

  • vector push_back overloads on the left and right values ​​of
template<class T, class Allocator = allocator<T>>
class vector {
public:
    void push_back(const T& x);
    void push_back(T&& x);
};
  • Directly into the literal, it creates temporary objects, but emplace_back not. All insert has a corresponding emplace.
  • emplace not necessarily faster than insert:
    • Adding value to the position occupied by an existing object
    • When set and check the map value exists, if there is value.
  • emplace function exists a risk when you call explicit constructor
std::vector<std::regex> v;
v.push_back(nullptr); // 编译出错
v.emplace_back(nullptr); // 能通过编译,运行时抛出异常,难以发现此问题 

Guess you like

Origin www.cnblogs.com/Asumi/p/12453025.html