玩命加载中 . . .

14.8-函数调用运算符


14.8 函数调用运算符

函数调用运算符必须是成员函数

struct absInt {
    int operator()(int val) const {
        return val < 0 ? -val : val;
    }
};

int i = -42;
absInt absObj;
int ui = absObj(i); // 42

absInt对象作用于一个实参列表,看起来像调用函数的过程
调用对象实际上是在运行重载的调用运算符

如果类定义了调用运算符,则该类的对象称作函数对象

含有状态的函数对象类

class PrintString
{
private:
    ostream &os;
    char sep;
public:
    PrintString(ostream& o = cout, char c = ' '): os(o), sep(c) {}
    void operator()(const string& s) const { os << s << sep; }
};

PrintString printer;
string s = "hello";
printer(s);     // 调用重载函数运算符

函数对象常常作为泛型算法的实参

vector<string> vs = {"kavin", "jack", "lisa"};
for_each(vs.begin(), vs.end(), PrintString(cout, '#'));
// kavin#jack#lisa#

14.8.1 lambda是函数对象

编译器会将lambda表达式翻译成一个未命名类的未命名对象
例如字符串排序的lambda表达式

vector<string> words = {"kavin", "jack", "lisa"};
stable_sort(words.begin(), words.end(), 
            [](const string& a, const string& b)
            { return a.size() < b.size(); });
// jack
// lisa
// kavin

其行为类似于下面这个类的一个未命名对象

class ShorterString {
public:
    bool operator()(const string& s1, const string& s2) const {
        return s1.size() < s2.size();
    }
};

默认情况下,lambda不能改变它捕获的对象,因此函数调用符是一个const成员函数,如果lambda被声明为可变的,则调用运算符就不是const的了

然后就可以用这个类替代lambda表达式,效果是一样的

stable_sort(words.begin(), words.end(), ShorterString());

如果是通过值捕获对象,那产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其使用捕获的变量的值来初始化数据成员

vector<string> words = {"joe", "jack", "lisa", "kavin"};
int sz = 5;
auto wc = find_if(words.begin(), words.end(), 
                [sz](const string& a)
                { return a.size() >= sz; });
cout << *wc << endl;
// kavin

生成的类将类似于

class SizeComp {
public:
    SizeComp(size_t n): sz(n) {}
    bool operator()(const string& s) const {
        return s.size() >= sz;
    }
private:
    size_t sz;
};

auto wc = find_if(words.begin(), words.end(), SizeComp(sz));
// kavin 效果是一样的

14.8.2 函数调用运算符

如果要让字符串从大到小排序

vector<string> words = {"jack", "lisa", "kavin"};
sort(svec.begin(), svec.end(), greater<string>());
// lisa
// kavin
// jack

指针也可以用

string s1 = "kavin";
string s2 = "jack";
string s3 = "lisa";

vector<string*> nameTable = {&s1, &s2, &s3};
sort(nameTable.begin(), nameTable.end(), greater<string*>());
for (auto& n : nameTable) cout << *n << endl;
// lisa
// jack
// kavin

14.8.3 可调用对象与function

有几种可调用的对象

  • 函数
  • 函数指针
  • lambda表达式
  • bind创建的对象
  • 重载了函数调用运算符的类

两种不同类型的可调用对象却可以共享同一种调用形式(call signature)
例如int(int, int)是一个函数类型,接受两个int,返回一个int

对于几个可调用对象共享同一种调用形式的情况,会希望把他们看成具有相同的类型

// 普通函数
int add(int i, int j) { return i + j;}
// lambda,其产生一个未命名的函数对象类
auto mod = [](int i, int j) { return i % j; };
// 函数对象类
struct divide {
    int operator()(int denominator, int devisor) {
        return denominator / devisor;
    }
};

利用这些可调用对象构建一个简单的桌面计算器,需要定义一个函数表用于存储指向这些可调用对象的“指针”,但是直接添加会有问题

map<string, int(*)(int, int)> binops;
binops.insert({"+", add});  // yes, add是一个指向函数的指针
binops.insert({"%", mod});  // err, mod不是一个函数指针,是一个对象

需要考虑如何消除这种错误

标准库function类型

function<T> f;  // f是一个用来存储可调用对象的空function

声明一个function类型,可以表示接受两个int,返回一个int的可调用对象

function<int(int, int)> f1 = add;   // 函数指针
function<int(int, int)> f2 = divide();  // 函数对象类的对象
function<int(int, int)> f3 = [](int i, int j)
                            { return i % j; };
cout << f1(4, 2) << endl;
cout << f2(4, 2) << endl;
cout << f3(4, 2) << endl;
// 6
// 2
// 0

这样就可以直接调用,不用管是函数指针,还是函数对象

map<string, function<int(int, int)>> binops = {
    {"+", add},             // 函数指针
    {"-", minus<int>()},    // 标准库函数对象
    {"/", divide()},        // 用户定义的函数对象
    {"*", [](int i, int j){return i * j;}}, // 未命名的lambda
    {"%", mod}              // 命名的lambda
};

cout << binops["+"](10, 5) << endl; // 15
cout << binops["-"](10, 5) << endl; // 5
cout << binops["/"](10, 5) << endl; // 2
cout << binops["*"](10, 5) << endl; // 50
cout << binops["%"](10, 5) << endl; // 0

重载的函数与function

不能(直接)将重载函数的名字存入function类型的对象中

int add(int i, int j) { return i + j; }
Sales_data add(const Sales_data&, const Sales_data&);
binops.insert({"+", add});  // err, 哪个add

解决二义性问题的一个途径是存储函数指针,换一个不重复的名字

int (*fp)(int, int) = add;
binops.insert({"+", fp});

也可以使用lambda来消除二义性

binops.insert({"+", [](int a, int b) {return add(a, b);}});

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录