读书笔记 《Effective modern C++》之 Moving to Modern C++(三)

Item 13: 优先使用const_iterators而不是iterators

使用cbegin和cend(c++11)。

// in container, find first occurrence of targetVal, then insert insertVal there
template<typename C, typename V>
void findAndInsert(C& container,  const V& targetVal, const V& insertVal)
{ 
      using std::cbegin;
      using std::cend;
      auto it = std::find(cbegin(container), // non-member cbegin
                          cend(container),   // non-member cend
                          targetVal);
      container.insert(it, insertVal);
}

自由函数cbegin和cend需要c++14。在c++11的代替方案是

template <class C>
auto cbegin(const C& container)->decltype(std::begin(container))
{
      return std::begin(container);
}

虽然c++11可以使用成员函数版本的cbegin,但是container是一个const的对象,因此使用begin也是返回的const_iterator,但不是每个容器都有成员函数版的cbegin。begin有数组特化的版本。

Item 14: 如果函数不会抛出异常,就要申明noexcept

根据函数是否有noexcept可以进行不同的操作(call不同的函数,或者说编译器对noexcept做更激进的优化),来达到优化的效果。

int f(int x) throw();   // C++98 style
int f(int x) noexcept;  // C++11 style
  • noexcept让编译器生成更好的目标代码

在没有noexcept的情况下,如果出现异常,调用栈会展开到函数f的caller,再进行一些无关的操作,然后结束程序。而又noexcept的栈只需要再运行结束时候才展开。因此带noexpect的函数的目标代码可以不带有处理栈展开的代码。

the call stack is unwound to f’s caller, and, after some actions not relevant here, program execution is terminated. With the C++11 exception specification, runtime behaviour is slightly different: the stack is only possibly unwound before program execution is terminated.

  • 根据是否noexcept决定调用行为
std::vector<Widget> vw;
Widget w;
vw.push_back(w);

典型的函数有std::move_if_noexcept。在vector中的成员函数push_back,在容器内存满了时候会再申请一块内存,因此需要对异常安全进行保证:往新的内存拷贝元素时,如果有异常抛出,vector的成员应该不变,因为直到全部元素被成功拷贝之前,旧地址的元素都不改变。但是把一个对象从就内存move到新内存上时可能抛出异常,而一但发生,就没法恢复成原先的状态了,所以只有保证移动构造函数为noexcept时候push_back才会使用move。

https://en.cppreference.com/w/cpp/container/vector/push_back

  • 三种noexcept的用法
  1. 放在函数后面,表示不抛出异常的保证
void f() noexcept;
void g(void pfa() noexcept);  // g takes a pointer to function that doesn't throw
  1. 条件性(conditionally) noexcept, 是否异常取决于noexcept(expression)中表达式的值
void (*fp)() noexcept(false); // fp points to a function that may throw

3.查看函数是否声明了noexcept(noexcept operator)
noexcept(函数表达式) -> 如果该函数时noexcept返回true

void no_throw() noexcept;
cout << std::boolalpha <<  "Is no_throw() noexcept? " << noexcept(no_throw()) << '\n'; 
// Is no_throw() noexcept? yes

void throw_() noexcept(!noexcept(no_throw())); // conditional noexcept & noexcept operator一起用
template <typename T, size_t N>
void swap(T (&a)[N], T (&b)[N]) noexcept(noexcept(swap(*a, *b)));

template <typename T1, typename T2>
struct pair {
    //...
    void swap(pair& p) noexcept(noexcept(swap(first, p.first)) &&
                                noexcept(swap(second, p.second)));
    // ...
};

C++11的析构和内存释放(delete)都是noexcept,如果需要关闭可以noexcept(false)

//doWork有noexcept,子函数没有noexcept,这也是合法的(子函数是C或c++98)
void setup(); // functions defined elsewhere
void cleanup();
void doWork() noexcept
{
      setup(); // set up work to be done
      //  .. do the actual work
      cleanup(); // perform cleanup actions
}

大部分还是异常中性的,因为就算函数不会抛异常,他们调用的函数可能会

https://www.boost.org/community/exception_safety.html
https://en.cppreference.com/w/cpp/language/noexcept

Item 15: 尽可能地使用constexpr

c++11 的constexpr函数很严格,不允许有if分支,只有return和三元表达式或者递归....
C++14就放宽了不少

// c++11
constexpr int pow(int base, int exp) noexcept
{
      return (exp == 0 ? 1 : base * pow(base, exp - 1));
}

constexpr int pow(int base, int exp) noexcept // C++14
{
      auto result = 1;
      for (int i = 0; i < exp; ++i) result *= base;
      return result;
}

constexpr 成员函数

c++11中,constexpr 的成员函数不能修改对象(const),且constexpr 的成员函数不能返回void。(与C++14中constexpr成员函数的区别)。

// c++14
class Point {
public:
      constexpr Point(double xVal = 0, double yVal = 0) noexcept
            : x(xVal), y(yVal)
      {}

      constexpr double xValue() const noexcept { return x; }
      constexpr double yValue() const noexcept { return y; }
      void setX(double newX) noexcept { x = newX; }
      void setY(double newY) noexcept { y = newY; }
private:
      double x, y;
};

constexpr Point p1(9.4, 27.7); // fine, "runs" constexpr ctor during compilation

constexpr
Point midpoint(const Point& p1, const Point& p2) noexcept
{
      return { (p1.xValue() + p2.xValue()) / 2,   // call constexpr member funcs
               (p1.yValue() + p2.yValue()) / 2 }; 
}
// init constexpr object w/result of constexpr function
constexpr auto mid = midpoint(p1, p2); 

Item 16: 保证带const的函数成员线程安全

const mutable
如果对象是const的时候,其的成员数据也无法修改,除非带有mutable关键字

struct Foo
{
   int var1;
   mutable int var2;
};

const Foo f{};
f.var1 = 10; // Not OK
f.var2 = 20; // OK

eg2
f() const 函数中的this-> T const *(指向的对象是const)

class temp {
    int value1; 
    mutable int value2;
public:
    void fun(int val) const
    {
        ((temp*) this)->value1 = 10; // cast away const-ness of this(it used to be temp const*)
        value2 = 10;
    }
};

mutable只改变指针指向对象的const-ness,改变指针本身的const-ness并不合理(都要mutable了为啥用const指针)

class A {
   const int * x; // x is non-const.  *x is const.
   int const * y; // y is non-const.  *y is const.
   int * const z; // z is const.  *z is non-const.
};

class A {
   mutable const int * x; // OK
   mutable int const * y; // OK
   mutable int * const z; // Doesn't make sense
};

在mutable const的情况下,const成员函数也会改变成员变量,导致线程的不安全。

mutex + lock_guard(RAII)

class Polynomial {
public:
      using RootsType = std::vector<double>;
      RootsType roots() const
      {
            std::lock_guard<std::mutex> g(m); // lock mutex
            if (!rootsAreValid) { // if cache not valid compute/store roots
                  rootsAreValid = true;
            }
            return rootVals;
      } // unlock mutex
private:
      mutable std::mutex m;
      mutable bool rootsAreValid{ false };
      mutable RootsType rootVals{};
};

和原子操作,原子操作虽然轻量,但不一定满足需求

class Point { // 2D point
public:

      double distanceFromOrigin() const noexcept // see Item 14 for noexcept
      { 
            ++callCount; // atomic increment
            return std::sqrt((x * x) + (y * y));
      }
private:
      mutable std::atomic<unsigned> callCount{ 0 };
      double x, y;
};

Item 17:特殊成员函数的生成机制

move的函数要编译器自己生成,必须没有

  • 析构函数的声明
  • 拷贝操作的声明
  • 移动操作的声明

加入log导致编译器不生成移动构造->影响性能

class StringTable {
public:
    StringTable() {
        makeLogEntry("Creating StringTable object");
    }
    ~StringTable() {
        makeLogEntry("Destroying StringTable object");
    }
private:
    std::map<int, std::string> values;
};
  • 默认构造: 没有用户定义的构造器时生成
  • 析构函数: 默认 noexcept, 当基类析构为virtual,其也为virtual
  • 拷贝构造: memberwise拷贝非static的数据。没用户声明的拷贝构造时候生成。如果用户声明了移动操作没声明拷贝,delete。不推荐在有用户定义的析构函数或者拷贝赋值函数时使用编译器生成的拷贝构造。(标记为deprecated)
  • 拷贝赋值: memberwise拷贝非static的数据。没用户定义的拷贝赋值,当有移动赋值时delete。不推荐在有用户定义的析构函数或者拷贝构造函数时使用编译器生成的拷贝构造。(标记为deprecated)
  • 移动赋值和移动构造: memberwise移动非static的数据。只有没有用户声明的拷贝操作,移动操作或析构函数时才自动生成。

posted @ 2020-07-31 15:27  linsinan1995  阅读(155)  评论(0)    收藏  举报