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

Item 7: 区分使用()和{}创建对象

三种常见的创建方式

int x(0); // initializer is in parentheses
int y = 0; // initializer follows "="
int z{ 0 }; // initializer is in braces

但是初始化成员变量有些不一样

class Widget {
private:
      int x{ 0 }; // fine, x's default value is 0
      int y = 0; // also fine
      int z(0); // error!
};

{}初始化禁止了初始化build-in类型时候的非显式转化。此外{}会避免一些因为vexing parse带来的歧义。

Widget w1(10); // call Widget ctor with argument 10
Widget w2();   // most vexing parse! declares a function
               // named w2 that returns a Widget! 函数声明。
Widget w3{}; // calls Widget ctor with no args

不过{}初始化可能带来一些意想不到的结果,比如有initializer_list构造函数的情况下。

class Widget {
public:
      Widget(int i, bool b);
      Widget(int i, double d); 
      Widget(std::initializer_list<long double> il); 
};

Widget w1(10, true); // uses parens and, calls first ctor
Widget w2{10, true}; // uses braces, calls std::initializer_list ctor (10 and true convert to long double)
Widget w3(10, 5.0); // uses parens and, calls second ctor
Widget w4{10, 5.0}; // uses braces, call std::initializer_list ctor (10 and 5.0 convert to long double)
class Widget {
public:
      Widget(int i, bool b); // as before
      Widget(int i, double d); // as before
      Widget(std::initializer_list<long double> il); // as before
      operator float() const; // convert to float
};

// 移动构造和复制构造产生非预期的结果
Widget w5(w4); // uses parens, calls copy ctor
Widget w6{w4}; // uses braces, calls std::initializer_list ctor (w4 converts to float, and float converts to long double)
Widget w7(std::move(w4)); // uses parens, calls move ctor
Widget w8{std::move(w4)}; // uses braces, calls, std::initializer_list ctor (for same reason as w6)

initializer_list构造函数在使用{}初始化被给予很高的优先级,甚至给与会发生narrow converting的不合法(braced initializer里被禁止)的initializer_list构造器

class Widget {
public:
      Widget(int i, bool b); // as before
      Widget(int i, double d); // as before
      Widget(std::initializer_list<bool> il); 
}; // conversion funcs

Widget w{10, 5.0}; // error! requires narrowing conversions

完全不合法时候才会给到普通的ctor

class Widget {
public:
      Widget(int i, bool b); // as before
      Widget(int i, double d); // as before
      Widget(std::initializer_list<std::string> il); // double或者int没法隐式转成string
};

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

空的{}初始化->默认ctor, 而不是一个为空的initializer_list构造器

Widget w2{}; // also calls default ctor
Widget w3(); // most vexing parse! declares a function!

使用为空的initializer_list构造器的正确姿势(想到了(伪)列表初始化queue,queue q{{1,2,3}}😉

Widget w4({}); // calls std::initializer_list ctor with empty list
Widget w5{{}}; // ditto

一些歧义

std::vector<int> v1(10, 20); // create 10-element, and all elements have the value of 20
std::vector<int> v2{10, 20}; // 列表初始化10,20

T localObject(std::forward<Ts>(params)...); // using parens
T localObject{std::forward<Ts>(params)...}; // using braces
// T 为std::vector<int>就有不一样的结果

Item 8: 优先使用nullptr而不是0或者NULL.

NULL可以被赋值给int, 产生歧义

void f(int); // three overloads of f
void f(bool);
void f(void*);
f(0); // calls f(int), not f(void*)
f(NULL); // might not compile, but typically calls, f(int). Never calls f(void*)
f(nullptr); // calls f(void*) overload

增加阅读性

auto result = findRecord( /* arguments */ );
if (result == nullptr) {
  // ...
}

避免模板使用时候的错误

int f1(std::shared_ptr<Widget> spw); // call these only when
double f2(std::unique_ptr<Widget> upw); // the appropriate
bool f3(Widget* pw); // mutex is locked

std::mutex f1m, f2m, f3m;

template<   typename FuncType,
            typename MuxType,
            typename PtrType>
decltype(auto) lockAndCall(FuncType func, 
                           MuxType& mutex,
                           PtrType ptr)
{
      MuxGuard g(mutex);
      return func(ptr);
}

auto result1 = lockAndCall(f1, f1m, 0); // error! -> PtrType推到成int
auto result2 = lockAndCall(f2, f2m, NULL); // error!-> PtrType推到成int
auto result3 = lockAndCall(f3, f3m, nullptr); // fine -> 推到成std::nullptr_t(nullptr的类型), f3可以把std::nullptr_t转为Widget*(可非显式转为任何指针类型)

Item 9: 优先使用别名声明(alias declarations)而不是typedef

typedef
      std::unique_ptr<std::unordered_map<std::string, std::string>> // typedef
      UPtrMapSS; // C++98

using UPtrMapSS =
      std::unique_ptr<std::unordered_map<std::string, std::string>>; // C++11, alias declaration

// 函数指针更可读性更高
typedef void (*FP)(int, const std::string&);
// same meaning as above
using FP = void (*)(int, const std::string&);

对模板类型进行重命名时,typedef必须要一个临时的结构体

// MyAllocList<T> is synonym for std::list<T, MyAlloc<T>>
template<typename T> // MyAllocList<T>
      using MyAllocList = std::list<T, MyAlloc<T>>;

MyAllocList<Widget> lw; // client code

// MyAllocList<T>::type is synonym for std::list<T, MyAlloc<T>>
template<typename T>
struct MyAllocList { 
      typedef std::list<T, MyAlloc<T>> type; 
}; 

MyAllocList<Widget>::type lw; // client code

如果想使用typedef在自身有模板的时候,需要typename

// Widget<T> contains a MyAllocList<T> as a data member
template<typename T>
class Widget { 
private: 
      typename MyAllocList<T>::type list; 
};

// using 简单多了
template<typename T>
      using MyAllocList = std::list<T, MyAlloc<T>>; // as before
template<typename T>
class Widget {
private:
      MyAllocList<T> list; // no "typename", no "::type"
};

编译器知道MyAllocList是一个non-dependent的类型,而不确定nested的MyAllocList::type是一个类型,所以要加上typename。

#include <type_traits>
std::remove_const<T>::type // C++11: const T → T
std::remove_const_t<T> // C++14 equivalent
std::remove_reference<T>::type // C++11: T&/T&& → T
std::remove_reference_t<T> // C++14 equivalent
std::add_lvalue_reference<T>::type // C++11: T → T&
std::add_lvalue_reference_t<T> // C++14 equivalent

// C++14 Standard
template <class T>
      using remove_const_t = typename remove_const<T>::type;

template <class T>

      using remove_reference_t = typename remove_reference<T>::type;
template <class T>
      using add_lvalue_reference_t = typename add_lvalue_reference<T>::type;

Item 10: 优先使用有界enums而不是无界enums

c++11加入了有界的enum class解决了枚举成员污染的问题

enum Color { black, white, red }; // black, white, red are in same scope as Color
auto white = false; // error! white already declared in this scope

enum class Color { black, white, red }; // black, white, red are scoped to Color
auto white = false; // fine, no other

Color c = white; // error! no enumerator named, "white" is in this scope
Color c = Color::white; // fine

enum class是禁止隐式转换的, enum能直接使用。

enum class Color { black, white, red }; // enum is now scoped
Color c = Color::red;

if (static_cast<double>(c) < 14.5) { // odd code, but it's valid suspect, but it compiles
      auto factors = primeFactors(static_cast<std::size_t>(c)); 
}

另一个有界enum的好处是,你可以定义enum成员的数据类型。在无界enum中,编译器自动选择最小的可适用类型。

enum Status {
    good = 0,
    failed = 1,
    incomplete = 100,
    corrupt = 200,
    indeterminate = 0xFFFFFFFF
};

有界enum可申明成员数据类型

enum class Status: std::uint32_t { 
      good = 0,
      failed = 1,
      incomplete = 100,
      corrupt = 200,
      audited = 500,
      indeterminate = 0xFFFFFFFF
};

有界enum不能隐式转换也会带来一些操作上的不便利,比如利用enum来跟踪tuple对象时候,无界的enum会更方便,而使用有界的enum时候,则需要做一次类型转化。

enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;
auto val = std::get<uiEmail>(uInfo); // get value of email field

有界enum时

enum class UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo; // as before
auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)> (uInfo);

这个问题可以借由std::underlying_type来获得有界enum的成员变量类型。使用constexpr是因为get的结果要在编译期就能推导出来。

// c++11 underlying_type<T>::type
template<typename E>
constexpr typename std::underlying_type<E>::type
toUType(E enumerator) noexcept
{
    return static_cast<
        typename std::underlying_type<E>::type>
        (enumerator);
}

// C++14 underlying_type_t<T>
template<typename E> 
constexpr std::underlying_type_t<E>
toUType(E enumerator) noexcept
{
      return static_cast<std::underlying_type_t<E>>(enumerator);
}


// C++14 auto
template<typename E> 
constexpr auto
toUType(E enumerator) noexcept
{
      return static_cast<std::und

erlying_type_t<E>>(enumerator);
}

auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);

Item 11: 优先使用delete关键字而不是把成员函数加private和不定义

Prefer deleted functions to private undefined ones.

使用delete来阻止其他人调用你不想被人调用的函数(编译器有时会自动生成你不想要的特殊成员函数)。在c++98里,basic_ios使用私有+未定义来处理这个问题。

template <class charT, class traits = char_traits<charT> >
class basic_ios: public ios_base {
public:
    ...
private:
    basic_ios(const basic_ios&);             // not defined
    basic_ios& operator=(const basic_ios&);  // not defined
};

而c++11的做法应该是使用delete,这样成员函数和友元函数在调用拷贝构造时候会在编译期报错,得到的报错信息会更好。

template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
public:
      basic_ios(const basic_ios& ) = delete;
      basic_ios& operator=(const basic_ios&) = delete;
};

delete是可以使用在任意的函数上的,还可以通过delete删除指定的隐式转化

bool isLucky(int number);
bool isLucky(int number); // original function
bool isLucky(char) = delete; // reject chars
bool isLucky(bool) = delete; // reject bools
bool isLucky(double) = delete; // reject doubles and floats

delete还可以用来删除指定的模板特化,这个操作在类的内部也可以在外部都可以。

template<typename T>
void processPointer(T* ptr);

template<>
void processPointer<void>(void*) = delete;
template<>
void processPointer<char>(char*) = delete;
// 完全禁止还需要加上const void*, const char*, wchar_t这些
template<>
void processPointer<const void>(const void*) = delete;
template<>
void processPointer<const char>(const char*) = delete;

Item 12: 使用override修饰覆盖(override)的函数

C++的面向对象的基础就是类的继承和虚函数的重写(override),允许把对基类的接口调用转到子类的重写函数上。
void doWork() & -> *this为左值时的调用,重写也要保证函数后方的引用是一样的

// 被覆盖的需要是虚函数,函数名相同(除了析构函数),函数参数相同,constness也相同,且子类的重写函数需要的返回类型和基类的函数相容
class Widget {
public:
      void doWork() &; // this version of doWork applies, only when *this is an lvalue
      void doWork() &&; // this version of doWork applies, only when *this is an rvalue
};

Widget makeWidget(); // factory function (returns rvalue)
Widget w; // normal object (an lvalue)

w.doWork(); // calls Widget::doWork for lvalues (i.e., Widget::doWork &)
makeWidget().doWork(); // calls Widget::doWork for rvalues (i.e., Widget::doWork &&)

使用override能让编译器在编译期报错,从而规避一些可能发生的错误。

class Base {
public:
    virtual void mf1() const;
    virtual void mf2(int x);
    virtual void mf3() &;
    void mf4() const;
};

// 编译警告
class Derived: public Base {
public:
    virtual void mf1();
    virtual void mf2(unsigned int x);
    virtual void mf3() &&;
    void mf4() const;
};

// 编译失败
class Derived: public Base {
public:
      virtual void mf1() override;
      virtual void mf2(unsigned int x) override;
      virtual void mf3() && override;
      virtual void mf4() const override;
};

函数后的&&还可以规避不必要的拷贝

class Widget {
public:
      using DataType = std::vector<double>;
      DataType& data() { return values; }
private:
      DataType values;
};

Widget w;
auto vals1 = w.data(); // copy w.values into vals1

// 假如有个工厂函数能够创建一个Widgets
Widget makeWidget();
auto vals2 = makeWidget().data(); // copy values inside the Widget into vals2, 对右值又进行了一次copy,浪费性能

可以用T f(T) & 和 T f(T) &&进行区分。

class Widget {
public:
      using DataType = std::vector<double>;
      DataType& data() & // for lvalue Widgets, return lvalue
      { return values; } 
      DataType data() && // for rvalue Widgets, return rvalue
      { return std::move(values); } 
private:
      DataType values;
};
auto vals1 = w.data(); // calls lvalue overload for Widget::data, copy ctor vals1
auto vals2 = makeWidget().data(); // calls rvalue overload for Widget::data, move ctor vals2

overload重载 、override覆盖、overwrite重写,final关键字(阻止子类重写)
https://blog.csdn.net/jszhangyili/article/details/7570311

posted @ 2020-07-28 22:07  linsinan1995  阅读(171)  评论(0)    收藏  举报