C++步步为营

Chapter 1: Accustoming Yourself

2019-07-17  本文已影响0人  世界上的一道风

Item 1: View C++ as a federation of languages.

Item 2: Prefer consts, enums, and inlines to #defines.

可以说是比起预处理器(preprocessor),更喜欢编译器(compiler)。


const double AspectRatio = 1.653;

常量定义通常放在头文件中,定义常量指针:

const std::string authorName("Scott Meyers");

要将常量的范围限制为类,必须使它成为成员,并且为了确保最多有一个常量的副本,您必须使它成为静态成员:

class GamePlayer {
private:
static const int NumTurns = 5; // constant declaration
int scores[NumTurns]; // use of constant
...
};

上面看到的是NumTurns的声明,而不是定义;
c++要求为所使用的任何东西提供一个定义,但是特定于类的常量是静态且是整型的(例如,整数、char、bools)时候是一个例外。
As long as you don’t take their address, you can declare them and use them without providing a definition
但是当去地址的时候,可以定义为:

const int GamePlayer::NumTurns; // definition of NumTurns;

将其放入实现文件中,而不是头文件中。
没有初始值,因为类常量的初值是在声明常量的地方提供的,所以在定义点不允许在设置初始值。
类内初始化只允许用于整数类型和常量。在不能使用上述语法的情况下,可以将初始值放在定义的地方:

class CostEstimate {
private:
static const double FudgeFactor; // declaration of static class
... // constant; goes in header file
};
const double // definition of static class
CostEstimate::FudgeFactor = 1.35; // constant; goes in impl. file
class GamePlayer {
private:
enum { NumTurns = 5 }; // “the enum hack” — makes
// NumTurns a symbolic name for 5
int scores[NumTurns]; // fine
...
};
template<typename T> // because we don’t
inline void callWithMax(const T& a, const T& b) // know what T is, we pass by reference-to const 
{ 
f(a > b ? a : b);
}

Item 3: Use const whenever possible.

void f1(const Widget *pw); // f1 takes a pointer to a
// constant Widget object
void f2(Widget const *pw); // so does f2
std::vector<int> vec;
...
const std::vector<int>::iterator iter =  vec.begin();  // iter acts like a T* const
*iter = 10; // OK, changes what iter points to
++iter; // error! iter is const

std::vector<int>::const_iterator cIter = vec.begin(); // cIter acts like a const T*
*cIter = 10; // error! *cIter is const
++cIter; // fine, changes cIter
class Rational { ... };

const Rational operator*(const Rational& lhs, const Rational& rhs);

Rational a, b, c;

//防止出现这样的错误
if (a * b = c) ... // oops, meant to do a comparison! 
  1. 提高c++程序性能的基本方法之一是通过reference-to-const来传递对象;
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // operator[] for
{ return text[position]; } // const objects
char& operator[](std::size_t position) // operator[] for
{ return text[position]; } // non-const objects
private:
std::string text;
};


TextBlock tb("Hello");
std::cout << tb[0]; // calls non-const TextBlock::operator[]
const TextBlock ctb("World");
std::cout << ctb[0]; // calls const TextBlock::operator[]


std::cout << tb[0]; // fine — reading a  non-const TextBlock
tb[0] = ’x’; // fine — writing a non-const TextBlock

std::cout << ctb[0]; // fine — reading a const TextBlock
ctb[0] = ’x’; // error! — writing a  const TextBlock

上面的成员函数定义后面加 const 是说明this指针是一个pointer to const object,所以函数里不能修改non-static data member的值,注意,static member是可以被修改的。其中 tb[0] = ’x’是传值,而且不应该修改成员函数返回类型。

例子如下:

class CTextBlock {
public:
...
char& operator[](std::size_t position) const     // inappropriate (but bitwise const) declaration of  operator[]
{ return pText[position]; } 
private:
char *pText;
};

为什么,因为该函数返回对对象内部数据的引用(returns a reference to the object’s internal data)。

const CTextBlock cctb("Hello"); // declare constant object
char *pc = &cctb[0]; // call the const operator[] to get a  pointer to cctb’s data
*pc = ’J’; // cctb now has the value “Jello”

bitwise cosntness成员函数改变了对象成员的值,因此引出logical constness:认为const成员函数可能会修改调用它的对象中的一些位,但只是以客户机无法检测到的方式。
比如这个例子, 需要记录当前长度是否合法、计算文本块的长度,:

class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
std::size_t textLength; // last calculated length of textblock
bool lengthIsValid; // whether length is currently valid
};


std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // error! can’t assign to textLength  and lengthIsValid in a const
lengthIsValid = true; 
} // member function
return textLength;
}

虽然textLength 和 lengthIsValid 声明不是const,是可修改的,但是常量成员函数并不能作用在这两个成员数据上,解决的方法是声明为mutable

class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength; // these data members may 
mutable bool lengthIsValid; // always be modified, even in
}; // const member functions


std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // now fine
lengthIsValid = true; // also fine
}
return textLength;
}
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // same as before
{
...
...
...
return text[position];
}
char& operator[](std::size_t position) // now just calls const op[]
{
return
const_cast<char&>( // cast away const on
// op[]’s return type;
static_cast<const TextBlock&>(*this) // add const to *this’s type;
[position] // call const version of op[]
);
}
...
};

如果你从一个const函数调用一个非const函数,你会冒着你承诺不修改的对象会被修改的风险。这就是为什么const成员函数调用非const成员函数是错误的:对象可以更改。这就是static_cast在这种情况下处理*this的原因:没有与常量相关的危险。

Things to Remember
✦ Declaring something const helps compilers detect usage errors. const can be applied to objects at any scope, to function parameters and return types, and to member functions as a whole.
✦ Compilers enforce bitwise constness, but you should program using logical constness.
✦ When const and non-const member functions have essentially identical implementations, code duplication can be avoided by having the non-const version call the const version.

Item 4: Make sure that objects are initialized before they’re used.

c++规则规定,对象的数据成员在输入构造函数体之前初始化,因此用成员初始化列表,初始化列表中的参数用作各数据成员的构造函数(复制构造、)参数:

ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
: theName(name),   // theName is copy-constructed from name
theAddress(address), // these are now all initializations
thePhones(phones),
numTimesConsulted(0)
{} // the ctor body is now empty

对于大多数类型,对复制构造函数的单个调用要比对默认构造函数的调用以及随后对复制赋值操作符的调用更有效——有时甚至更有效。

通常最好通过成员初始化来初始化所有内容。 类似地,即使希望默认构造数据成员,也可以使用成员初始化列表;只需要不指定任何初始化参数,因为当用户定义类型的数据成员在成员初始化列表中没有初始化器时,编译器会自动调用这些数据成员的默认构造函数:

ABEntry::ABEntry()
: theName(), // call theName’s default ctor;
theAddress(), // do the same for theAddress;
thePhones(), // and for thePhones;
numTimesConsulted(0) // but explicitly initialize
{}
class FileSystem { // from your library’s header file
public:
...
std::size_t numDisks() const; // one of many member functions
...
};
extern FileSystem tfs; // declare object for clients to use
// (“tfs” = “the file system” ); definition
// is in some .cpp file in your library


class Directory { // created by library client
public:
Directory( params );
...
};
Directory::Directory( params )
{
...
std::size_t disks = tfs.numDisks(); // use the tfs object
...
}

解决办法:单例模式(Singleton pattern):所要做的就是将每个非本地静态对象移动到它自己的函数中,在那里它被声明为静态。这些函数返回对它们包含的对象的引用。然后客户机调用函数而不是引用对象。换句话说,非本地静态对象被替换为本地静态对象。

class FileSystem { ... }; // as before
FileSystem& tfs() // this replaces the tfs object; it could be
{ // static in the FileSystem class
static FileSystem fs; // define and initialize a local static object
return fs; // return a reference to it
}
class Directory { ... }; // as before
Directory::Directory( params ) // as before, except references to tfs are
{ // now to tfs()
...
std::size_t disks = tfs().numDisks();
...
}
Directory& tempDir() // this replaces the tempDir object; it
{ // could be static in the Directory class
static Directory td( params ); // define/initialize local static object
return td; // return reference to it
}

Things to Remember
✦ Manually initialize objects of built-in type, because C++ only sometimes initializes them itself.
✦ In a constructor, prefer use of the member initialization list to assignment inside the body of the constructor. List data members in the initialization list in the same order they’re declared in the class.
✦ Avoid initialization order problems across translation units by replacing non-local static objects with local static objects.

上一篇下一篇

猜你喜欢

热点阅读