读书笔记 《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
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
#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