C++ primer 第十四章-重载操作符和Class-type转换

Hi!这里是山幺幺的c++ primer系列。写这个系列的初衷是,虽然在学校学习了c++,但总觉得对这门语言了解不深,因此我想来啃啃著名的c++ primer,并在这里同步记录我的学习笔记。由于我啃的是英文版,所以笔记是中英文夹杂的那种。另外由于已有一定的编程基础,所以这个系列不会含有太基础的知识,想入门的朋友最好自己啃书嘻嘻~

基础概念


overloaded operator

  • overloaded operator function的参数个数
    • 若不是成员函数:与operator的operand数相同
    • 若是成员函数:=operator的operand数 - 1,因为左操作数bound to this指针
  • 除了 overloaded function-call operator(即operator()),an overloaded operator may not have default arguments
  • an operator function must either be a member of a class or have at least one parameter of class type
// error: cannot redefine the built-in operator for ints
int operator+(int, int);
  • 能/不能被重载的操作符一览
  • 只能重载已存在的操作符,不能发明新的操作符
  • 重载不改变操作符的优先级和结合性
  • 调用重载操作符的两种方式
data1 + data2; // 调用operator+的法一
operator+(data1, data2); // 调用operator+的法二

data1 += data2; // 调用operator+=的法一
data1.operator+=(data2); // 调用operator+=的法二
  • 最好不要重载的
    • 最好不要重载&&、||、,:它们本来 guarantee the order in which operands are evaluated 和 short -circuit evaluation properties,重载后则不再保证这些(因为重载操作符相当于函数调用),所以最好不要重载它们
    • 最好不要重载&、,:the language defines what the comma and address-of operators mean when applied to objects of class type;because these operators have built-in meaning, they ordinarily should not be overloaded
  • 重载的设计建议
    • 必须重载为成员函数的:assignment (=)、subscript ([])、call (())、member access arrow (->)
    • 建议重载为成员函数的:compound-assignment、increment、decrement、dereference
    • 建议重载为非成员函数的:symmetric operators(即左右操作数类型可以互换)(比如arithmetic、equality、relational、bitwise operators)
    string s = "world";
    string t = s + "!"; // ok: we can add a const char* to a string
    string u = "hi" + s; // would be an error if + were a member of string
    

Input and Output Operators


重载 <<

  • 原本的<<:左操作数是一个 reference to a nonconst ostream object(是引用是因为we cannot copy an ostream object);返回its ostream parameter
  • 栗子
ostream &operator<<(ostream &os, const Sales_data &item) {
  os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
  return os;
}
  • 设计的注意事项:
    • 一般而言output operators should print the contents of the object, with minimal formatting(比如should not print a newline),从而让user可以设计details of their output
    • I/O operators必须是非成员函数:因为第一个参数是i/ostream类型
    • I/O operators usually must be declared as friends:因为通常要访问nonpublic成员

重载 >>

  • 原本的>>:左操作数是一个 reference to a stream;返回a reference to its given stream
  • 栗子
istream &operator>>(istream &is, Sales_data &item) {
  double price; // no need to initialize; we'll read into price before we use it
  is >> item.bookNo >> item.units_sold >> price;
  if (is) // check that the inputs succeeded
    item.revenue = item.units_sold * price;
  else
    item = Sales_data(); // input failed: give the object the default state
  return is;
}
  • input过程中可能发生的错误
    • the stream contains data of an incorrect type
    • hit end-of -file or some other error on the input stream
  • 设计的注意事项:
    • input operators should decide what, if anything, to do about error recovery
    • usually an input operator should set only the failbit,表示读入的数据格式不正确(不能set badbit,因为badbit would indicate that the stream was corrupted;不能set eofbit,因为eofbit would imply that the file was exhausted;这两个bit都应该交给IO library itself to indicate)
    • I/O operators必须是非成员函数:因为第一个参数是i/ostream类型
    • I/O operators usually must be declared as friends:因为通常要访问nonpublic成员

Arithmetic and Relational Operators


arithmetic operators

  • classes that define an arithmetic operator generally define the corresponding compound assignment operator as well,因为重载arithmetic operator的函数体中通常会使用重载的compound assignment operator
// arithmetic operator
Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) {
  Sales_data sum = lhs; // copy data members from lhs into sum
  sum += rhs; // add rhs into sum
  return sum;
}
// compound assignment operator
Sales_data& Sales_data::operator+=(const Sales_data &rhs) {
  units_sold += rhs.units_sold;
  revenue += rhs.revenue;
  return *this;
}

equality operators

bool operator==(const Sales_data &lhs, const Sales_data &rhs) {
  return lhs.isbn() == rhs.isbn() &&
  lhs.units_sold == rhs.units_sold &&
  lhs.revenue == rhs.revenue;
}

bool operator!=(const Sales_data &lhs, const Sales_data &rhs) {
  return !(lhs == rhs);
}

relational operators

  • 设计原则:if the class also has ==, define < only if the definitions of
    < and == yield consistent results(即:不相等的两个对象,必然有一个<另一个)

Assignment Operators


普通assignment operators

  • 作用:allow other types as the right-hand operand(copy- and move-assignment operators要求左右两边都是该类)
  • 特点:assignment operators, regardless of parameter type, must be defined as member function;should return a reference to its left -hand operand
  • 栗子:这个assignment可以不用check for self-assignment,因为左右是不同的类型,不可能用于self-assignment
StrVec &StrVec::operator=(initializer_list<string> il) {
  // alloc_n_copy allocates space and copies elements from the given range
  auto data = alloc_n_copy(il.begin(), il.end());
  free(); // destroy the elements in this object and free the space
  elements = data.first; // update data members to point to the new space
  first_free = cap = data.second;
  return *this;
}

compound-assignment operators

  • 特点:compound assignment operators are not required to be members(但建议设计为成员);should return a reference to their left-hand operand

Subscript Operator


  • 特点:subscript operator must be a member function;通常returns a reference to the element that is fetched(这样subscript就可以出现在赋值语句的任意一边了);通常define both const and nonconst versions of subscript
  • 栗子
class StrVec {
public:
  std::string& operator[](std::size_t n) { return elements[n]; }
  const std::string& operator[](std::size_t n) const { return elements[n]; }
private:
  std::string *elements; // pointer to the first element in the array
};

Increment and Decrement Operators


特点

  • 建议重载为成员,因为它们change the state of the object on which they operate
  • should define both the prefix and postfix versions

prefix increment/decrement operators

  • 特点:return a reference to the incremented or decremented object
  • 栗子
// prefix: return a reference to the incremented/decremented object
StrBlobPtr& StrBlobPtr::operator++() {
  // if curr already points past the end of the container, can't increment it
  check(curr, "increment past end of StrBlobPtr");
  ++curr; // advance the current state
  return *this;
}

StrBlobPtr& StrBlobPtr::operator--() {
  // if curr is zero, decrementing it will yield an invalid subscript
  --curr; // move the current state back one element
  check(-1, "decrement past begin of StrBlobPtr");
  return *this;
}

post increment/decrement operators

  • 特点:为与prefix区分,postfix versions take an extra (unused) parameter of type int,使用它时,编译器supplies 0 as the argument for this parameter;return the old (unincremented or undecremented) value(是value不是reference)
  • 栗子
StrBlobPtr StrBlobPtr::operator++(int) {
  // no check needed here; the call to prefix increment will do the check
  StrBlobPtr ret = *this; // save the current value
  ++*this; // advance one element; prefix ++ checks the increment
  return ret; // return the saved state
}

StrBlobPtr StrBlobPtr::operator--(int) {
  // no check needed here; the call to prefix decrement will do the check
  StrBlobPtr ret = *this; // save the current value
  --*this; // move backward one element; prefix -- checks the decrement
  return ret; // return the saved state
}

StrBlobPtr p(a1); // p points to the vector inside a1
p.operator++(0); // call postfix operator++
p.operator++(); // call prefix operator++

Member Access Operators


操作符

  • ->
  • *

特点

  • operator arrow must be a member
  • dereference operator is not required to be a member but usually should be a member

栗子

class StrBlobPtr {
public:
  std::string& operator*() const { 
    auto p = check(curr, "dereference past end");
    return (*p)[curr]; // (*p) is the vector to which this object points
  }
  std::string* operator->() const { // delegate the real work to the dereference operator
    return & this->operator*();
  }
};

operator arrow的特殊性质

  • 其他操作符都可以被重载来做任何事,比如+可以被重载为-,但we cannot change the fact that arrow fetches a member,arrow operator never loses its fundamental meaning of member access
  • 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
  • point->mem的执行步骤
    • 若point是一个指针,则apply built-in arrow operator:相当于执行 (*point).mem; (step 1)
    • 若point是an object of a class that defines operator->,则相当于执行point.operator()->mem; :the result of point.operator->() is used to fetch mem. If that result is a pointer, then step 1 is executed on that pointer. If the result is an object that itself has an overloaded operator->(), then this step is repeated on that object. (step 2)

Function-Call Operator


作用

  • classes that overload the call operator allow objects of its type to be used as if they were a function

栗子

class PrintString {
public:
  PrintString(ostream &o = cout, char c = ' '): os(o), sep(c) { }
  void operator()(const string &s) const { os << s << sep;
}
private:
  ostream &os; // stream on which to write
  char sep; // character to print after each output
};

PrintString printer; // uses the defaults; prints to cout
printer(s); // prints s followed by a space on cout
PrintString errors(cerr, '\n');
errors(s); // prints s followed by a newline on cerr
for_each(vs.begin(), vs.end(), PrintString(cerr, '\n')); // print each element in vs to cerr followed by a newline

特点

  • function-call operator must be a member function

lambda本质上是function object

  • When we write a lambda, the compiler translates that expression into an unnamed object of an unnamed class
    1. lambda没有capture时
    stable_sort(words.begin(), words.end(), [](const string &a, const string &b) { return a.size() < b.size();});
    
    上面代码实际上等价于
    class ShorterString {
    public:
      bool operator()(const string &s1, const string &s2) const { return s1.size() < s2.size(); }
    };
    
    stable_sort(words.begin(), words.end(), ShorterString());
    
    PS:因为lambda默认不能change their captured variables,所以是const函数;If the lambda is declared as mutable, then the call operator is not const
    1. lambda capture by value时:因为variables that are captured by value are copied into the lambda,所以classes generated from lambdas that capture variables by value have data members corresponding to each such variable,且会用捕获的变量值对它们进行初始化
    // get an iterator to the first element whose size() is >= sz
    auto wc = find_if(words.begin(), words.end(), [sz](const string &a)
    
    等价于
    class SizeComp {
      SizeComp(size_t n): sz(n) { } // parameter for each captured variable
      // call operator with the same return type, parameters, and body as the lambda
      bool operator()(const string &s) const { return s.size() >= sz; }
    private:
      size_t sz; // a data member for each variable captured by value
    };
    
    auto wc = find_if(words.begin(), words.end(), SizeComp(sz));
    
    1. lambda capture by reference时:the compiler is permitted to use the reference directly without storing that reference as a data member in the generated class
  • classes generated from a lambda expression have a deleted default constructor, deleted assignment operators, and a default destructor

library-defined function objects

  • standard library defines a set of classes,包括
  • 用法
plus<int> intAdd; // function object that can add two int values
int sum = intAdd(10, 20); // equivalent to sum = 30

// passes a temporary function object that applies the > operator to two strings
sort(svec.begin(), svec.end(), greater<string>());
  • 它们对指针有效
vector<string *> nameTable; // vector of pointers
// error: the pointers in nameTable are unrelated, so < is undefined
sort(nameTable.begin(), nameTable.end(), [](string *a, string *b) { return a < b; });
// ok: library guarantees that less on pointer types is well defined
sort(nameTable.begin(), nameTable.end(), less<string*>());
  • associative containers use less<key_type> to order their elements

Callable Objects


分类

  • 函数
  • 指向函数的指针
  • lambda
  • objects created by bind
  • classes that overload the function-call operator

特点

  • 每个callable object都有其类型,比如each lambda has its own unique (unnamed) class type
  • 不同类型的callable objects可能有相同的call signature(即the type returned by a call to the object and the argument type(s) that must be passed in the call;栗子:int(int, int))
  • 即使call signature相同,因为类型不同,以下写法的最后一行也是不正确的
// ordinary function
int add(int i, int j) { return i + j; }
// lambda, which generates an unnamed function-object class
auto mod = [](int i, int j) { return i % j; };
// function-object class
struct div {
  int operator()(int denominator, int divisor) {
    return denominator / divisor;
  }
};
// maps an operator to a pointer to a function taking two ints and returning an int
map<string, int(*)(int,int)> binops;

binops.insert({"+", add}); // ok: add is a pointer to function of the appropriate type
binops.insert({"%", mod}); // error: mod is not a pointer to function

function类


作用

  • 解决上面那段代码的问题,把call signature相同的callable object集成到一个map中

操作一览

栗子

function<int(int, int)> f1 = add; // function pointer
function<int(int, int)> f2 = div(); // object of a function-object class
function<int(int, int)> f3 = [](int i, int j) { return i * j; }; // lambda

cout << f1(4,2) << endl; // prints 6
cout << f2(4,2) << endl; // prints 2
cout << f3(4,2) << endl; // prints 8

解决上上段代码问题的办法

map<string, function<int(int, int)>> binops = {
  {"+", add}, // function pointer
  {"-", std::minus<int>()}, // library function object
  {"/", div()}, // user-defined function object
  {"*", [](int i, int j) { return i * j; }}, // unnamed lambda
  {"%", mod}  // named lambda object
};

binops["+"](10, 5); // calls add(10, 5)
binops["-"](10, 5); // uses the call operator of the minus<int> object
binops["/"](10, 5); // uses the call operator of the div object
binops["*"](10, 5); // calls the lambda function object
binops["%"](10, 5); // calls the lambda function object

注意

  • 不能直接store the name of an overloaded function in an object of type function
int add(int i, int j) { return i + j; }
Sales_data add(const Sales_data&, const Sales_data&);
map<string, function<int(int, int)>> binops;
binops.insert( {"+", add} ); // error: which add?
  • 解决方法一:使用指向函数的指针
int (*fp)(int,int) = add; // pointer to the version of add that takes two ints
binops.insert( {"+", fp} ); // ok: fp points to the right version of add
  • 解决方法二:使用lambda
// ok: use a lambda to disambiguate which version of add we want to use
binops.insert( {"+", [](int a, int b) {return add(a, b);} } );

Class-type Conversions(即user-defined conversions,由class类型转为其他类型)


conversion operator

  • 作用:a special kind of member function that converts a value of a class type to a value of some other type
  • 格式:operator type() const;
    PS:这里的type可以是任何可作为函数返回类型的类型(void除外);type不能是an array or a function type
  • 要求
    • 必须是成员函数
    • 不能有返回类型(返回值必须是type类型的)
    • 形参list必须为空(因为通常是被隐式调用的)
    • 通常是常函数
  • 栗子
class SmallInt {
public:
  SmallInt(int i = 0): val(i) {
    if (i < 0 || i > 255) throw std::out_of_range("Bad SmallInt value");
  }
  operator int() const { return val; }
private:
  std::size_t val;
};

SmallInt si;
si = 4; // implicitly converts 4 to SmallInt then calls SmallInt::operator=
si + 3; // implicitly converts si to int followed by integer addition

// the double argument is converted to int using the built-in conversion
SmallInt si = 3.14; // calls the SmallInt(int) constructor
// the SmallInt conversion operator converts si to int;
si + 3.14; // that int is converted to double using the built-in conversion

explicit conversion operators

  • 一个危险的栗子:if istream had a conversion to bool,那么下面这段代码就是合法的:首先用bool conversion operator 把 cin 转为 bool,随后the resulting bool value would then be promoted to int,最后the promoted bool value (either 1 or 0) would be shifted left 42 positions
int i = 42;
cin << i; // this code would be legal if the conversion to bool were not explicit!
  • 作用:避免上面那个危险的栗子发生
  • 用法
class SmallInt {
public:
  // the compiler won't automatically apply this conversion
  explicit operator int() const { return val; }
  // other members as before
};

SmallInt si = 3; // ok: the SmallInt constructor is not explicit
si + 3; // error: implicit is conversion required, but operator int is explicit
static_cast<int>(si) + 3; // ok: explicitly request the conversion
  • 一个例外:标了explicit但被用作如下用途的表达式会隐式转换
    • the condition of an if, while, or do statement
    while (std::cin >> value) // cin is implicitly converted to bool by the istream operator bool conversion function
    
    • the condition expression in a for statement header
    • operand to the logical NOT (!), OR (||), or AND (&&) operators
    • the condition expression in a conditional (?:) operator
  • 使用建议:operator bool ordinarily should be defined as explicit

避免模糊转换

  • 情形一
// usually a bad idea to have mutual conversions between two class types
struct B;
struct A {
  A() = default;
  A(const B&); // converts a B to an A
};

struct B {
  operator A() const; // also converts a B to an A
};

A f(const A&);
B b;

// 错误写法
A a = f(b); // error ambiguous: f(B::operator A()) or f(A::A(const B&))
// 正确写法
A a1 = f(b.operator A()); // ok: use B's conversion operator
A a2 = f(A(b)); // ok: use A's constructor

PS:注意we can’t resolve the ambiguity by using a cast,因为cast也会面临ambiguity的问题

  • 情形二
struct A {
  A(int = 0); // usually a bad idea to have two
  A(double); // conversions from arithmetic types
  operator int() const; // usually a bad idea to have two
  operator double() const; // conversions to arithmetic types
};

void f2(long double);
A a;
f2(a); // error ambiguous: f(A::operator int()) or f(A::operator double())
long lg;
A a2(lg); // error ambiguous: A::A(int) or A::A(double)

// 这样是可以的
short s = 42;
// promoting short to int is better than converting short to double
A a3(s); // uses A::A(int)
  • 情形三
struct C {
  C(int);
};
struct D {
  D(int);
};

void manip(const C&);
void manip(const D&);
manip(10); // error ambiguous: manip(C(10)) or manip(D(10))
  • 情形四:只要有two different user-defined conversions could be used,就是模糊的,哪怕这两个转换的代价有差异,即:the rank of an additional standard conversion (if any) matters only if the viable functions require the same userdefined conversion
struct E {
  E(double);
};
void manip2(const C&);
void manip2(const E&);
// error ambiguous: two different user-defined conversions could be used
manip2(10); // manip2(C(10) or manip2(E(double(10)))
  • 情形五
class SmallInt {
  friend SmallInt operator+(const SmallInt&, const SmallInt&);
public:
  SmallInt(int = 0); // conversion from int
  operator int() const { return val; } // conversion to int
private:
  std::size_t val;
};

SmallInt s1, s2;
SmallInt s3 = s1 + s2; // uses overloaded operator+
int i = s3 + 0; // error: ambiguous,可以convert 0 to a SmallInt and use the SmallInt version of +或者convert s3 to int and use the built-in addition
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349