本篇博客是Scott Meyers的EffectiveModernC++读书笔记.刚写了几章笔记,还没写完.可以这里下载本书pdf,密码6aoo.本博客的目的是1)方便大家快速刷一下本书;2)博主备忘.
第一章 Deducing Types.
Item 1: Understand template type deduction
观察下面的例子,在调用*cout<<f1(a)<<endl;*的时候程序员不需要显示标明类型,C++进行了类型自动推理
template <typename T>
T f1(T p)
{
p = p + 1;
return p;
}
int main()
{
double a = 3;
cout << f1(a) << endl;
return 0;
}
本章讲了一些推理的情况,属于手册形式.推理大部分是符合直觉的,不过如果你的直觉和设计C++的大神不一样,就得改一下直觉,哈哈.书中有这几种情况
1) param type is reference or point, but not universal reference.
例子1
template<typename T>
void f(T& param); // param is a reference
int x = 27; f(x); //推理成 int&
const int cx = x;f(cs); //推理成 const int&
const int& rx = x;f(rx); //推理成 const int&
例子2
template<typename T>
void f(const T& param); // param is now a ref-to-const
int x = 27; f(x);// 推理成 const int&
const int cx = x; f(cs); // 推理成 const int&
const int& rx = x; f(rx); // 推理成 const int&
例子3
template<typename T>
void f(T* param); // param is now a pointer
int x = 27; f(&x);// 推理成 int*
const int *px = &x; f(px) // 推理成 const int*
2) param type is universal reference, universal reference是这个样子 T&&, 是右值引用的意思,参考stl中move的方法.
例子1
template<typename T>
void f(T&& param); // param is now a universal reference
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // x是左值,推理成 int&
f(cx); // cx是左值,推理成 const int
f(rx); // rx是左值,推理成 const int&
f(27); // 27是右值,推理成 int&&
3) ParamType is Neither a Pointer nor a Reference
例子1
template<typename T>
void f(T param); // param is now passed by value
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // 推理成 int
f(cx); // 推理成 int
f(rx); // 推理成 int
ps:这里有些反直觉
例子2
template<typename T>
void f(T param); // param is still passed by value
const char* const ptr = "Fun with pointers";// ptr is const pointer to const object
f(ptr); // 推理成const char*
ps: const让人头疼,不过这里感觉确实应该这样推理.
4) array
例子1
template<typename T>
void f(T param); // template with by-value parameter
const char name[] = "J. P. Briggs"; // name's type is const char[13]
f(name); //推理成 const char*
例子2
template<typename T>
void f(T& param); // template with by-reference parameter
const char name[] = "J. P. Briggs"; // name's type is const char[13]
f(name); //推理成 const char& [13]
ps:如果我们想的数组的长度,可以:
int name[10];
//方法1:
int length_name = sizeof(name)/sizeof(name[0]);
//方法2
int length_name2 = std::size(name);
//这个std::size的源码是这样
template<class _Ty,size_t _Size>
inline constexpr size_t size(const _Ty(&)[_Size]) _NOEXCEPT
{
// get dimension for array
return (_Size);
}
5) Function Arguments
例子1
void someFunc(int, double); // someFunc is a function;type is void(int, double)
template<typename T>
void f1(T param); // in f1, param passed by value
template<typename T>
void f2(T& param); // in f2, param passed by ref
f1(someFunc); //推理成 void (*)(int, double)
f2(someFunc); // 推理成 void (&)(int, double)
本节总结:感觉是一些C++冷知识.
Item 2: Understand auto type deduction
auto用法
auto x = 3;
//等价于
int x = 3;
1)
auto可以用上一节的原理来推
auto x = 27;
//相当于这种写法
template<typename T>
void func_for_x(T param);
func_for_x(27);
const auto cx = x;
//相当于这种写法
template<typename T>
void func_for_cx(const T param);
func_for_cx(x);
const auto& rx = x;
//相当于这种写法
template<typename T>
void func_for_rx(const T& param);
func_for_rx(x);
例子1
auto x = 27; //等价于 int x = 27;
const auto cx = x; //等价于 const int cx = x;
const auto& rx = x; //等价于 const int& rx = x;
auto&& uref1 = x; //等价于 int& uref1 = x;
auto&& uref2 = cx; //等价于 const int& uref2 = x;
auto&& uref3 = 27; //等价于int&& uref3 = 27;
const char name[] = "R. N. Briggs";
auto arr1 = name; //等价于 const char*
auto& arr2 = name; // 等价于 const char (&)[13]
void someFunc(int, double); // someFunc is a function type is void(int, double)
auto func1 = someFunc; // 等价于 void (*)(int, double)
auto& func2 = someFunc; // 等价于 void (&)(int, double)
Item 3: Understand decltype
decltype这个关键字这样用
decltype(auto) f1()
{
int x = 0;
return x; // decltype(x) is int, so f1 returns int
}
decltype(auto) f2()
{
int x = 0;
return (x); // decltype((x)) is int&, so f2 returns int&
}
decttype返回变量的类型, 下面是举例
const int i = 0; // decltype(i) is const int
bool f(const Widget& w); // decltype(w) is const Widget& ,decltype(f) is bool(const Widget&), decltype(f(w)) is bool
struct Point {
int x, y; // decltype(Point::x) is int
};
Widget w; // decltype(w) is Widget
vector<int> v; // decltype(v) is vector<int>
if (v[0] == 0) // decltype(v[0]) is int&
返回模板类型
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
{
return c[i];
}
体会一下这句话
*auto specifies that the type is to be deduced, and decltype says that decltype rules should be used during the deduction.*
看一下下面这个例子
template<typename Container, typename Index>
auto authAndAccess_rvalue(Container& c, Index i)
{
return c[i];
}
template<typename Container, typename Index>
decltype(auto) authAndAccess_ref(Container& c, Index i)
{
return c[i];
}
int main()
{
int w;
vector<int> buf(3);
for (auto &value : buf) value = 1;
int a = authAndAccess_rvalue<vector<int>, int>(buf, 1); //这个是右值
authAndAccess_ref<vector<int>, int>(buf, 1) = 3; //这个是引用
return 0;
}
看一下这个例子
Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // auto type deduction,myWidget1's type is Widget
decltype(auto) myWidget2 = cw; // decltype type deduction,myWidget2's type is const Widget&
ps: decltype有些反直觉,用的时候还是自己测试一下左值还是右值
Item 4: Know how to view deduced types.
IDE Editors
下面例子推理成什么呢
const int theAnswer = 42;
auto x = theAnswer;
auto y = &theAnswer;
这是我用vs2015提示的结果
const int theAnswer = 42;
auto x = theAnswer; //提示为int
auto y = &theAnswer; //提示为 const int*
据说有些太复杂的逻辑IED也会推理错,所以建议太复杂自己也弄不清的时候还是换个写法吧,要是领导也看不懂说你dull怎么办呢.
如果编译器弹出问题,可以分析下也可以改一个简单的写法
实在不行,可以打印一下类型
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int x = 0;
cout << typeid(x).name() << endl;
return 0;
}
书中还说有时候typeid也不好使,推荐用boost库的方法
#include <boost/type_index.hpp>
template<typename T>
void f(const T& param)
{
using std::cout;
using boost::typeindex::type_id_with_cvr;
// show T
cout << "T = "<< type_id_with_cvr<T>().pretty_name()<< '\n';
// show param's type
cout << "param = "<< type_id_with_cvr<decltype(param)>().pretty_name()<< '\n';
}
第二章 auto
Item 5: Prefer auto to explicit type declarations.
atuo的变量必须进过初始化
int x1; // 未初始化
auto x2; // error! initializer required
auto x3 = 0; // fine, x's value is well-define
auto 和 function比较,auto速度快一些
auto derefLess = // C++14 comparison
[](const auto& p1, // function for
const auto& p2) // values pointed
{ return *p1 < *p2; }; // to by anything pointer-like
std::function<bool(const std::unique_ptr<Widget>&,
const std::unique_ptr<Widget>&)>
derefUPLess = [](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{ return *p1 < *p2; };
例子1 std::vector<int>::size_type在32位系统写时32位,在64位系统下是64位
#include <iostream>
#include <vector>
using namespace std;
int main()
{
std::vector<int> v;
unsigned sz = v.size(); //这种写法如果数组的内容条数超时32位的范围,在64位系统就会产生错误
auto asz = v.size(); //这种写法看起来高端一些
return 0;
}
例子2:下边例子写法1没有写法2好的原因
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
std::unordered_map<std::string, int> m;
//写法1
for (const std::pair<std::string, int>& p : m)
{
}
//写法2
for (const auto& p : m)
{
}
return 0;
}
unordered_map的key是 const std::string, 但是循环中是std::string, 所以变态的编译器会偷偷的创建一个临时变量,这样就浪费性能了,如果用auto,编译器和人类都省事了.书中在这个小结中说到
相比python,c++是有些难用,但是对于高手来说是没啥问题的.我看到想说,python真香.作者: 如果代码看起来不直观,你就看一下IDE的提示,保持一些抽象还是必要滴,重构的时候也方便.^_^也许高手和我们不是同一种生物吧.
Item 6: Use the explicitly typed initializer idiom when auto deduces undesired types
例子1
class Widget {/*Some features*/};
std::vector<bool> features(const Widget& w);// Define priority function
bool highPriority = features(w)[5]; // Get priority
processWidget(w, highPriority); // According to priority, carry out relevant processing
但是如果写成auto, 我在vs2015下测试就没有得到预期结果
#include <iostream>
#include <vector>
using namespace std;
std::vector<bool> features(const int& w)
{
vector<bool> buf(10);
for (auto &value : buf) value = true;
return buf;
}
int main()
{
int w;
auto highPriority = features(w)[5]; //注意,highPriority会赋值为false,下面的写法会改正确
return 0;
}
书中说应该写成下面这种写法就对了
#include <iostream>
#include <vector>
using namespace std;
std::vector<bool> features(const int& w)
{
vector<bool> buf(10);
for (auto &value : buf) value = true;
return buf;
}
int main()
{
int w;
auto highPriority = static_cast<bool>(features(w)[5]); //这里highPriority是true了
return 0;
}
*bool highPriority = features(w)[5];* 这行代码 features(w)[5] 会返回引用对象,我们用bool去接,编译器会自动把bool值copy过来.
但是如果改用auto, features(w)[5] 是一个 tmp object 的 index 5, 返回的是 *dangling pointer*, c++标注对这种情况是*未定义*.
例子2
Matrix sum = m1 + m2 + m3 + m4;
每一个 + 的结果不直接回 Matrix type,而是类似 Sum<Matrix, Matrix> ,整个则是 Sum<Sum<Sum<Matrix, Matrix>,Matrix>,Matrix>。
这样的 invisible proxy classes 通常和 ‘auto’ 处得不好。所以要避免用 auto 去接。若真的要用 ‘auto’ 去接也要使用 static_cast<type> 来避免 dangling pointer。例如:
auto sum = static_cast<Matrix>(m1 + m2 + m3 + m4);
第三章 moving to modern C++
如果本书只看一张的话,那就是本章了.赶紧看一下吧.
Item 7: Distinguish between () and {} when creating objects.
c++的初始化居然可以四种写法,这里有孔乙己的画面.
int x(0); // initializer is in parentheses
int y = 0; // initializer follows "="
int z{ 0 }; // initializer is in braces
int z = { 0 }; // initializer uses "=" and braces
注意这几个的不同
Widget w1; // call default constructor
Widget w2 = w1; // not an assignment; calls copy ctor
w1 = w2; // an assignment; calls copy operator=
估计你也不知道 constructor/copy constructor/copy operator=的不同
#include<iostream>
#include<stdio.h>
using namespace std;
class Test
{
public:
Test() {} //this is default constructor
Test(const Test &t)
{
cout<<"Copy constructor called "<<endl;
}
Test& operator = (const Test &t)
{
cout<<"Assignment operator called "<<endl;
return *this;
}
};
// Driver code
int main()
{
Test t1, t2;
t2 = t1;
Test t3 = t1;
getchar();
return 0;
}
这几种初始化方法还不能混用
class Widget {
private:
int x{ 0 }; // fine, x's default value is 0
int y = 0; // also fine
int z(0); // error!
};
std::atomic<int> ai1{ 0 }; // fine
std::atomic<int> ai2(0); // fine
std::atomic<int> ai3 = 0; // error!
作者说如果这时你傻掉了,那就用{}, 他把{}称为*Uniform initialization*. {}, 英文叫 braced initialization, 它的作用是禁止narrowing conversions among built-in types.
double x, y, z;
int sum1{ x + y + z }; // error! sum of doubles may not be expressible as int
int sum2(x + y + z); // okay (value of expression truncated to an int)
int sum3 = x + y + z; // okay
c++有一个vexing(烦人)的地方(作者在书中用这个词)
Widget w1(10); // call Widget constructor with argument 10
Widget w2(); //???, 是调用无参数构造函数,还是调动一个叫w2的函数
Widget w3{}; //这种写法就好了,只能是调用无参数构造函数
使用 brace initialization有个需要注意的地方是,如果类定义了initializer_list, 就会优先调用initilazer_list的函数.比如这个
class Widget {
public:
Widget(int i, bool b); // as before
Widget(int i, double d); // as before
Widget(std::initializer_list<long double> il); // added
};
Widget w1(10, true); // uses parens and, as before,calls first ctor
Widget w2{ 10, true }; // uses braces, but now calls std::initializer_list ctor (10 and true convert to long double)
Widget w3(10, 5.0); // uses parens and, as before,calls second ctor
Widget w4{ 10, 5.0 }; // uses braces, but now calls std::initializer_list ctor (10 and 5.0 convert to long double)
如果你对上面有疑问,说明你对 initializer_list这个关键字不清楚. initializer_list是一种构造函数, 例如这样构造
vector<int> v1 = {1,2,3,4};
上面的内部逻辑是:
1 创建临时变量 std::initializer_list<T> l = {1,2,3,4}
2 v1.insert(v1.end(), l.begin(), l.end())
例子1
#include <iostream>
#include <vector>
#include <initializer_list>
template <class T>
struct S {
std::vector<T> v;
S(std::initializer_list<T> l) : v(l) {
std::cout << "constructed with a " << l.size() << "-element list\n";
}
};
int main()
{
S<int> s = { 1, 2, 3, 4, 5 }; // copy list-initialization
}
输出: constructed width a 5 element list
例子2
struct myclass {
myclass (int,int);
myclass (initializer_list<int>);
/* definitions ... */
};
myclass foo {10,20}; // calls initializer_list ctor
myclass bar (10,20); // calls first constructor
参数列表只要可以记性 narrowing conversions, 编译器就会调用 initializer_list, 如果initializer_list是string, 你传int或double,编译器就能避免再调用string的initializer_list.
class Widget {
public:
Widget(int i, bool b); // as before
Widget(int i, double d); // as before
// std::initializer_list element type is now std::string
Widget(std::initializer_list<std::string> il);
}; // conversion funcs
Widget w1(10, true); // uses parens, still calls first ctor
Widget w2{ 10, true }; // uses braces, now calls first ctor
Widget w3(10, 5.0); // uses parens, still calls second ctor
Widget w4{ 10, 5.0 }; // uses braces, now calls second ctor
如果传一个空的{}, 这时也有initilizer_list, C++规定此时调用默认构造函数
class Widget {
public:
Widget(); // default ctor
Widget(std::initializer_list<int> il); // std::initializer_list ctor
}; // conversion funcs
int main()
{
Widget w1; // calls default ctor
Widget w2{}; // also calls default ctor
Widget w3(); // most vexing parse! declares a function!
}
如果我此时就是想调用initializer_list constructor, 可以这样
Widget w4({}); // calls std::initializer_list ctor with empty list
Widget w5{{}}; // calls std::initializer_list ctor with empty list
关于vector用法的举例
std::vector<int> v1(10, 20); // 插入10个int,值为20
std::vector<int> v2{10, 20}; // 插入10和20这两个int
书中在这里提醒: 如果你重构的时候增加一个intializer_list constructor, 就会产生一些问题,要注意.
Things to Remember
• Braced initialization is the most widely usable initialization syntax, it prevents narrowing conversions, and it’s immune to C++’s most vexing parse.
• During constructor overload resolution, braced initializers are matched to std::initializer_list parameters if at all possible, even if other constructors offer seemingly better matches.
• An example of where the choice between parentheses and braces can make a significant difference is creating a std::vector<numeric type> with two arguments.
• Choosing between parentheses and braces for object creation inside templates can be challenging.
Item 8: Prefer nullptr to 0 and NULL.
本章作者推荐使用关键字 nullptr, 能用nullptr就不用0或NULL
Things to Remember
• Prefer nullptr to 0 and NULL.
• Avoid overloading on integral and pointer types
Item 9: Prefer alias declarations to typedefs.
新的概念*alias declarations*
typedef void (*FP)(int, const std::string&); // typedef
using FP = void (*)(int, const std::string&); // alias declaration
但是 *alias declarations*支持模板用法
template<typename T> // MyAllocList<T>
using MyAllocList = std::list<T, MyAlloc<T>>; // is synonym for std::list<T,MyAlloc<T>>
MyAllocList<Widget> lw; // client code
//使用typedef就比较麻烦了
template<typename T>
struct MyAllocList {
typedef std::list<T, MyAlloc<T>> type;
};
MyAllocList<Widget>::type lw; // client code
Things to Remember
• typedefs don’t support templatization, but alias declarations do.
• Alias templates avoid the “::type” suffix and, in templates, the “typename”
prefix often required to refer to typedefs.
• C++14 offers alias templates for all the C++11 type traits transformations.
Item 10: Prefer scoped enums to unscoped enums
推荐使用新的enum语法,新的enum语法有范围
enum Color { black, white, red };
auto white = false; // error! white already declared in this scope
enum Class Color { black, white, red };
auto white = false; // fine, no other "white" in scope
Color c = white; // error! no enumerator named "white" is in this scope
Color c = Color::white; // fine
auto c = Color::white; // also fine
Things to Remember
• C++98-style enums are now known as unscoped enums.
• Enumerators of scoped enums are visible only within the enum. They convert to other types only with a cast.
• Both scoped and unscoped enums support specification of the underlying type. The default underlying type for scoped enums is int. Unscoped enums have no default underlying type.
• Scoped enums may always be forward-declared. Unscoped enums may be forward-declared only if their declaration specifies an underlying type
Item 11: Prefer deleted functions to private undefined ones.
对容易混淆的参数调用,可以使用delete关键字
class Man {
public:
bool isLucky(int number) { return true; }; // original function
bool isLucky(char) = delete; // reject chars
bool isLucky(bool) = delete; // reject bools
};
int main()
{
Man lili;
lili.isLucky(1);
lili.isLucky(true); //编译错误
}
可以对模板中具体的类进行delete
class Widget {
public:
template<typename T>
void processPointer(T* ptr)
{
…
}
};
template<>
void Widget::processPointer<void>(void*) = delete;
Things to Remember
• Prefer deleted functions to private undefined ones.
• Any function may be deleted, including non-member functions and templateinstantiations.
Item 12: Declare overriding functions override
推荐使用overrdid关键字,如果自己继承错误了,会收到编译器的警告
class Base {
public:
virtual void mf1() const;
virtual void mf2(int x);
};
class Derived : public Base {
public:
virtual void mf1() const override;
virtual void mf2(double x) override; //这里会有警告,编译器找不到能继承的函数
};
Item 13: Prefer const_iterators to iterators
在使用iterator的地方,要考虑一下是不是可以用const_iterator.只有好处没有坏处.
例子: 在一个vector中找到20, 如果找到了,就再它前面插入0,如果没有找到,就再vector最后插入0
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec_buf = { 1,2,3,4 };
vector<int>::const_iterator it = find(vec_buf.cbegin(), vec_buf.cend(), 20);
vec_buf.insert(it, 0);
}
Item 14: Declare functions noexcept if they won’t emit exceptions.
使用noexcept关键字,编译器会给额外的优化,函数速度会更快一点点.
int f(int x) throw(); // no exceptions from f: C++98 style
int f(int x) noexcept; // no exceptions from f: C++11 style
增加noexcept关键字,在设计上也看起来高大上了,调用者也会感觉nice. 注意用上这个关键字不是说编译器就不会产生exception了,而是开发者告诉编译器,我保证这个函数不会有异常,你给我再优化一下吧.
Things to Remember
• noexcept is part of a function’s interface, and that means that callers may depend on it.
• noexcept functions are more optimizable than non-noexcept functions.
• noexcept is particularly valuable for the move operations, swap, memory deallocation functions, and destructors.
• Most functions are exception-neutral rather than noexcept.
Item 15: Use constexpr whenever possible.
constexpr 关键字可以让函数变成宏,在编译期算出结果
constexpr int foo(int i)
{
return i * 5.0;
}
int main()
{
foo(5) //返回常量,编译时计算
int i = 10;
foo(i); //返回非常量,运行时计算
}
Item 16: Make const member functions thread safe
mutable 关键字
如果一个类是const的,类的成员就不能改变了,如果我想让类的其中一个成员改变,就可以把这个成员声明成mutable
#include <iostream>
using namespace std;
code
class Test {
public:
int a;
mutable int b;
Test(int x=0, int y=0) {
a=x;
b=y;
}
void seta(int x=0) {
a = x;
}
void setb(int y=0) {
b = y;
}
void disp() {
cout<<endl<<"a: "<<a<<" b: "<<b<<endl;
}
};
int main() {
const Test t(10,20);
cout<<t.a<<" "<<t.b<<"\n";
// t.a=30; //Error occurs because a can not be changed, because object is constant.
t.b=100; //b still can be changed, because b is mutable.
cout<<t.a<<" "<<t.b<<"\n";
return 0;
}
atomic 关键字
一个比mutex更底层的关键字,比mutex底层,不blockthread, 没有上下文切换,速度会快一点点.
#include <iostream>
#include <vector>
#include <thread>
#include <atomic>
using namespace std;
atomic<int> g_sum = 0;
void SumNumbers(const vector<int>& toBeSummed, const int idxStart, const int idxEnd)
{
for (int i = idxStart; i <= idxEnd; ++i)
{
g_sum += toBeSummed[i];
}
}
int main()
{
vector<int> toBeSummed;
for (int i = 0; i < 30000; ++i)
{
toBeSummed.push_back(rand());
}
int sum = 0;
for (int i = 0; i < toBeSummed.size(); ++i)
{
sum += toBeSummed[i];
}
cout << "sum = " << sum << endl;
thread t1(SumNumbers, toBeSummed, 0, 9999);
thread t2(SumNumbers, toBeSummed, 10000, 19999);
thread t3(SumNumbers, toBeSummed, 20000, 29999);
t1.join();
t2.join();
t3.join();
cout << "sum = " << g_sum << endl;
return 0;
}
Item 17: Understand special member function generation.
下面是这本书的目录,我还没有看后边的.希望我不要太监.
第四章 Smart Pointers
第五章 Rvalue References, Move Semantics, and Perfect Forwarding
第六章 Lambda Expressions
第七章 The Concurrency API
第八章 Tweaks