1、数组元素和容器元素的初始化
vector<string> svec(5); |
The compiler initializes svec by first using the default string constructor to create a temporary value. The copy constructor is then used to copy the temporary into each element of svec.string strs[5] = {string("ff"), string("22")};
第一、二个元素用拷贝构造函数初始化,其余的用默认构造函数初始化
2、禁止复制
By declaring (but not defining) a private copy constructor, we can forestall any attempt to copy an object of the class type: Attempts to make copies in user code will be flagged as an error at compile time, and attempts to make copies in member functions or friends will result in an error at link time.
3、复制构造函数、赋值运算符、析构函数
A useful rule of thumb is that if a class needs a destructor, it will also need the assignment operator and a copy constructor.
This rule is often referred to as the Rule of Three.
An important difference between the destructor and the copy constructor or assignment operator is that even if we write our own destructor, the synthesized destructor is still run.
Best Practice:The assignment operator often does the same work as is needed in the copy constructor and destructor. In such cases, the common work should be put in private utility functions.
4.运算符重载
An overloaded operator must have at least one operand of class or enumeration type. This rule enforces the requirement that an overloaded operator may not redefine the meaning of the operators when applied to objects of built-in type.
Overloaded operators make no guarantees about the order in which operands are evaluated. In particular, the operand-evaluation guarantees of the built-in logical AND, logical OR, and comma operators are not preserved. Both operands to an overloaded version of && or || are always evaluated. The order in which those operands are evaluated is not stipulated. The order in which the operands to the comma are evaluated is also not defined. For this reason, it is usually a bad idea to overload &&, || , or the comma operator.
5.Overloading the Output Operator <<
To be consistent with the IO library, the operator should take an ostream& as its first parameter and a reference to a const object of the class type as its second. The operator should return a reference to its ostream parameter.ostream& operator <<(ostream& os, const ClassType &object)
{
os << // ...
return os;
}
The ostream is non const because writing to the stream changes its state. The parameter is a reference because we cannot copy an ostream object.
6. Overloading the Input Operator >>
A more important, and less obvious, difference between input and output operators is that input operators must deal with the
possibility of errors and end-of-file.istream& operator>>(istream& in, Sales_item& s)
{
double price;
in >> s.isbn >> s.units_sold >> price;
// check that the inputs succeeded
if (in)
s.revenue = s.units_sold * price;
else
s = Sales_item(); // input failed: reset object to default state
return in;
}
Our Sales_item input operator reads the expected values and checks whether an error occurred. The kinds of errors that might happen include:
- Any of the read operations could fail because an incorrect value was provided. For example, after reading isbn , the input operator assumes that the next two items will be numeric data. If nonnumeric data is input, that read and any subsequent use of the stream will fail.
- Any of the reads could hit end-of-file or some other error on the input stream.
Rather than checking each read, we check once before using the data we read. If an input operator detects that the input failed, it is often a good idea to make sure that the object is in a usable and consistent state. Doing so is particularly important if the object might have been partially written before the error occurred.
In addition to handling any errors that might occur, an input operator might need to set the condition state of its input istream parameter.Our input operator is quite simple, the only errors we care about are those that could happen during the reads. If the reads succeed, then our input operator is correct and has no need to do additional checking.
Some input operators do need to do additional checking. For example, our input operator might check that the isbn we read is in an appropriate format. We might have read data successfully, but these data might not be suitable when interpreted as an ISBN. In such cases, the input operator might need to set the condition state to indicate failure, even though technically speaking the actual IO was successful. Usually an input operator needs to set only the failbit. Setting eofbit would imply that the file was exhausted, and setting badbit would indicate that the stream was corrupted. These errors are best left to the IO library itself to indicate.
7. Subscript Operator
One complication in defining the subscript operator is that we want it to do the right thing when used as either the left- or right-hand operand of an assignment. To appear on the left-handside, it must yield an lvalue, which we can achieve by specifying the return type as a reference. As long as subscript returns a reference, it can be used on either side of an assignment.
It is also a good idea to be able to subscript const and non const objects. When applied to a const object, the return should be a const reference so that it is not usable as the target of an assignment.
Ordinarily, a class that defines subscript needs to define two versions: one that is a non const member and returns a reference and one that is a const member and returns a const reference.class Foo
{
public:
int &operator[] (const size_t);
const int &operator[] (const size_t) const;
// other interface members
private:
vector<int> data;
// other member data and private utility functions
};
int& Foo::operator[] (const size_t index)
{
return data[index]; // no range checking on index
}
const int& Foo::operator[] (const size_t index) const
{
return data[index]; // no range checking on index
}
8. Member Access Operators
To support pointerlike classes, such as iterators, the language allows the dereference ( * ) and arrow ( ->) operators to be overloaded. The dereference and arrow operators are often used in classes that implement smart pointers.// private class for use by ScreenPtr only
class ScrPtr
{
friend class ScreenPtr;
Screen *sp;
size_t use;
ScrPtr(Screen *p): sp(p), use(1) { }
~ScrPtr() { delete sp; }
};
class ScreenPtr
{
public:
// no default constructor: ScreenPtrs must be bound to an object
ScreenPtr(Screen *p): ptr(new ScrPtr(p)) { }
// copy members and increment the use count
ScreenPtr(const ScreenPtr &orig):
ptr(orig.ptr) { ++ptr->use; }
ScreenPtr& operator=(const ScreenPtr& rhs)
{
++rhs.ptr->use; // increment use count on rhs first
if (--ptr->use == 0)
delete ptr; // if use count goes to 0 on this object, delete it
ptr = rhs.ptr;
return *this;
}
~ScreenPtr() { if (--ptr->use == 0) delete ptr; }
private:
ScrPtr *ptr;
};
Among the fundamental operations a pointer supports are dereference and arrow. We can give our class these operations as follows:class ScreenPtr
{
public:
// constructor and copy control members as before
Screen &operator*() { return *ptr->sp; }
Screen *operator->() { return ptr->sp; }
const Screen &operator*() const { return *ptr->sp; }
const Screen *operator->() const { return ptr->sp; }
private:
ScrPtr *ptr;
};
Operator arrow is unusual. It may appear to be a binary operator that takes an object and a member name, dereferencing the object in order to fetch the member. Despite appearances, the arrow operator takes no explicit parameter.
There is no second parameter because the right-hand operand of -> is not an expression. Rather, the right-hand operand is an identifier that corresponds to a member of a class. There is no obvious, useful way to pass an identifier as a parameter to a function. Instead, the compiler handles the work of fetching the member. The overloaded arrow operator must return either a pointer to a class type or an object of a class type that defines its own operator arrow. When we write point->action()
, the compiler evaluates this code as follows:
- If point is a pointer to a class object that has a member named action , then the compiler writes code to call the action member of that object.
- Otherwise, if point is an object of a class that defines operator-> , then point->action is the same as point.operator->()->action. That is, we execute operator->() on point and then repeat these three steps, using the result of executing operator-> on point.
- Otherwise, the code is in error.
9. Arithmetic and Relational Operators
最佳实践:将+、-、<、>等操作符定义为非成员函数,因为它们不改变对象的状态;将+=等定义为成员函数,因为它们改变了对象的状态。Sales_item operator+(const Sales_item& lhs, const Sales_item& rhs)
{
Sales_item ret(lhs); // copy lhs into a local object that we'll return
ret += rhs; // add in the contents of rhs
return ret; // return ret by value
}
注意,+重载操作符调用了+=重载操作符。同样的,==和!=也应该只实现一套逻辑,让其中一个去调用另外一个。
10. Call Operator and Function Objects
class GT_cls { |
The standard library defines a set of arithmetic, relational, and logical function-object classes. The library also defines a set of function adaptors that allow us to specialize or extend the function-object classes defined by the library or those that we define ourselves. The library function-object types are defined in the functional header.
using a library function object with the algorithms:sort(svec.begin(), svec.end(), greater<string>());
The standard library provides a set of function adaptors with which to specialize and extend both unary and binary function objects. The function adaptors are divided into the following two categories.
- Binders: A binder is a function adaptor that converts a binary function object into a unary function object by binding one of the operands to a given value.
- Negators: A negator is a function adaptor that reverses the truth value of a predicate function object.
The library defines two binder adaptors: bind1st and bind2nd . Each binder takes a function object and a value. As you might expect, bind1st binds the given value to the first argument of the binary function object, and bind2nd binds the value to the second. For example, to count all the elements within a container that are less than or equal to 10 , we would pass count_if the
following:count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10));
The third argument to count_if uses the bind2nd function adaptor. That adaptor returns a function object that applies the <= operator using 10 as the right-hand operand. This call to count_if counts the number of elements in the input range that are less than or equal to 10.
The library also provides two negators: not1 and not2 . Again, as you might expect, not1 reverses the truth value of a unary predicate function object, and not2 reverses the truth value of a binary predicate function object.
To negate our binding of the less_equal function object, we would writecount_if(vec.begin(), vec.end(), not1(bind2nd(less_equal<int>(), 10)));
Here we first bind the second operand of the less_equal object to 10 , effectively transforming that binary operation into a unary operation. We then negate the return from the operation using not1 . The effect is that each element will be tested to see if it is <= to 10. Then, the truth value of that result will be negated. In effect, this call counts those elements that are not <= to 10.
11. Defining the Increment/Decrement Operators
class CheckedPtr { |