Featured image of post C++面向对象高级开发-兼谈对象模型-转换函数、智能指针、迭代器、仿函数与命名空间

C++面向对象高级开发-兼谈对象模型-转换函数、智能指针、迭代器、仿函数与命名空间

转换函数, 非explicit单实参构造函数, 转换函数与非explicit单实参构造函数, explicit单实参构造函数, 转换函数的模板偏特化实例, 智能指针, 迭代器, 仿函数, namespace命名空间

C++面向对象高级开发-兼谈对象模型-转换函数、智能指针、迭代器、仿函数与命名空间

Notes

  • 转换函数
  • 非explicit单实参构造函数
  • 转换函数与非explicit单实参构造函数
  • explicit单实参构造函数
  • 转换函数的模板偏特化实例
  • 智能指针
  • 迭代器
  • 仿函数
  • namespace命名空间

转换函数

#include <iostream>
#include <cstdlib>

class Fraction {
public:
    Fraction(int num, int den =1) : m_numerator(num), m_denominator(den) { }
    operator double() const {
        return (double)(m_numerator/m_denominator);
    }
private:
    int m_numerator;    //分子
    int m_denominator;  //分母
};

int main(int argc, const char* argv[]) {
    Fraction f(3,5);
    double d = 4+f;     //调用operator double()将f转换为0.6
    std::cout << d << std::endl;
    return EXIT_SUCCESS;
}

若要将类对象进行类型转换,可增加转换函数处理类型转换。如上述代码中的Fraction分数类,其结果分子除以分母做为double值是允许的。operator double() const函数即允许将Fraction类转换为double值。转换函数的参数列表均为空。返回值类型不需写出。函数名即为返回值类型。转换函数不改变数据值,故通常增加const关键字。当执行到double d = 4+f;时,编译器会寻找是否有全局操作符+重载,若存在首参为整型,第二个参数为Fraction类对象时,优先调用全局操作符重载。若无全局操作符重载,则尝试f转换为double即可通过语法规范。则3/5转换为0.6后加上4赋给d。关于转换函数,在合理情况下,其数量不限。

非explicit单实参构造函数

#include <iostream>
#include <cstdlib>

class Fraction {
public:
    Fraction(int num, int den=1):m_numerator(num), m_denominator(den) { }
    Fraction operator+(const Fraction& f) {
        return Fraction(...);
    }
private:
    int m_numerator;
    int m_denominator;
};

int main(int argc, const char* argv[]) {
    Fraction f(3,5);
    Fraction d2 = f + 4;    //调用non-explicit ctor将4转换为Fraction(4,1)后调用operator+
    return EXIT_SUCCESS;
}

Argument(Actual Parameter)指的是函数调用时的实际参数。Parameter是指函数定义中参数,而Argument指的是函数调用时的实际参数。Parameter=形参(formal parameter)Argument=实参(actual parameter)。Fraction类的构造函数为两个Parameters,一个argument。一个argument代表为一个实参即可。编译器读至语句Fraction d2 = f + 4;寻找operator+的操作符重载,其参数列表中为与Fraction相加,语句中为4非Fraction类,编译器尝试转换4为Fraction类。由非explicit单实参构造函数,可将4传入构造函数,第二个参数为默认值。故非explicit单实参构造函数可将其他类型的对象转换为此类对象。

转换函数与非explicit单实参构造函数

#include <iostream>
#include <cstdlib>

class Fraction {
public:
    Fraction(int num, int den=1) : m_numerator(num), m_denominator(den) { }
    operator double() const {
        return (double)(m_numerator/m_denominator);
    }
    Fraction operator + (const Fraction& f) {
        return Fraction(...);
    }
private:
    int m_numerator;
    int m_denominator;
};

int main(int argc, const char * argv[]) {
    Fraction f(3,5);
    Fraction d2 = f + 4; // [ERROR] ambiguous
    return EXIT_FAILURE;
}

同上,编译器读至Fraction d2 = f+4;时,通过operator+尝试将4转换为Fraction类对象,语法规范通过。或将f通过转换函数operator double() const转换为double后相加使用Fraciton非explicit单实参构造函数后也可通过语法规范检测。此时造成歧义,两种方法均可通过,编译器报错。

explicit单实参构造函数

#include <iostream>
#include <cstdlib>

class Fraction {
public:
    explicit Fraction(int num, int den=1) : m_numerator(num), m_denominator(den) { }
    operator double() const {
        return (double)(m_numerator/m_denominator);
    }
    Fraction operator + (const Fraction& f) {
        return Fraction(...);
    }
private:
    int m_numerator;
    int m_denominator;
};

int main(int argc, const char * argv[]) {
    Fraction f(3,5);
    Fraction d2 = f + 4; // [ERROR] conversion from 'double' to 'Fraction' requested.
    return EXIT_FAILURE;
}

此时构造函数前含有explicit关键字,其意义为当新建类对象时才调用构造函数,不可以通过类型转换来调用构造函数。此时执行语句Fraction d2 = f + 4;编译器不会再将4通过构造函数转换为Fraction类对象。explicit关键字通常情况下用在构造函数前。还可能在模板中用到,后续笔记补充。

转换函数的模板偏特化实例

template <class Alloc>
class vector<bool, Alloc> {
public:
    typedef __bit_reference reference;
protected:
    reference operator [] (size_type n) {
        return *(begin() + difference_type(n));
    }
    ...
};

struct __bit_reference {
    unsigned int *p;
    unsigned int mask;
    ...
public:
    operator bool() const { return !(!(*p & mask)); }
    ...
}

容器类中存放的元素为Boolean值,其operator[]函数为取指定位置的boolean值,返回类型为reference,定义为__bit_reference,并非boolean值,此设计方法为Proxy代理。故struct __bit_reference中自然含有bool类型的转换函数operator bool() const

智能指针

C++ Class完成后的类对象与函数或指针相似。与指针相似的类对象类可称为pointer-like classes。与指针类似的应用之一即为智能指针。智能指针扩充了原指针的功能。 智能指针

上图中的代码右侧的指向图中,较大的圆圈为智能指针,智能指针中一定含有普通指针。shared_ptr类为模板类,故其指针px类型为T*。对于指针而言,其常用操作符为*->shared_ptr<Foo> sp(new Foo);语句即为调用shared_ptr类的构造函数,参数传入的Foo对象将值赋值给px,使用者即可以通过Foo f(*sp);sp->method();的方法调用调用智能指针。语句sp->method();中,操作符->作用至sp,调用shared_ptr类中操作符重载函数T* operator -> () const。该函数直接返回指针px。故原语句可直接替换为px->method();,在C++中,->符号所得结果会持续作用。通过查阅标准库可知,std::shared_ptr是通过指针保持对象共享所有权的智能指针,多个 shared_ptr对象可占有同一对象。详细参考链接

迭代器

template <class T>
struct __list_node {
    void* prev;
    void* next;
    T data;
};

template <class T, class Ref, class Ptr>
struct __list_iterator {
    typedef __list_iterator<T, Ref, Ptr> self;
    typedef Ptr pointer;
    typedef Ref reference;
    typedef __list_node<T>* link_type;
    link_type node;
    bool operator == (const self& x) const { return node == x.node; }
    bool operator != (const self& x) const { return node != x.node; }
    reference operator * () const {
    return (*node).data;
    }
    pointer operator -> () const {
    return &(operator*());
    }
    self& operator ++ () {
        node = (link_type)((*node).next);
        return *this;
    }
    self operator ++ (int) {
        self tmp = *this;
        ++*this;
        return tmp;
    }
    self& operator -- () {
        node = (link_type)((*node).prev);
        return * this;
    }
    self operator -- (int) {
        self tmp = *this;
        --*this;
        return tmp;
    }
};

迭代器为指向对象的一个元素,所以与智能指针/指针类似。迭代器不仅需要处理智能指针所含有的对*操作符与->操作符的重载,还需对==!=++--等操作符进行重载。 迭代器

代码中的link_type node与图中的node相同,由typedef类型__list_node<T>*可知node含有data与前后双向指针。对迭代器使用*符号即为取当前节点的data值。对迭代器使用->符号与*意义相同,由于operator*()已取得对象值,则operator->()只需取得其指针返回即可。

仿函数

C++ Class完成后的类对象与函数或指针相似。与函数相似的类对象类可称为function-like classes。操作符()可被称为function call operator,即函数调用操作符。对()操作符重载,即为与函数类似的类。

template <class T1, class T2>
struct pair {
    T1 first;
    T2 second;
    pair() : first(T1()), second(T2()) { }
    pair(const T1& a, const T2& b) : first(a), second(b) { }
    ...
};

template <class T>
struct identity : public unary function<T,T> {
    const T& operator () (const T& x) const {
    return x;
    }
};

template <class Pair>
struct select1st : public unary_function<Pair, typename Pair::first_type> {
    const typename Pair::first_type& operator() (const Pair& x) const {
        return x.first;
    }
};

template <class Pair>
struct select2nd : public unary_function<Pair, typename Pair::second_type> {
    const typename Pair::second_type& operator() (const Pair& x) const {
        return x.second;
    }
};

代码中的三个struct均对操作符()重载,即为仿函数。struct identity为身份认证,传入x,传回x。 select1st<Pair>()()调用操作符重载函数const typename Pair::first_type& operator(const Pair& x) const返回x.first的值。再看另一段仿函数的代码:

template <class T>
struct plus : public binary_function<T, T, T> {
    T operator() (const T& x, const T& y) const {
    return x + y;
    }
};

template <class T>
struct minus : public binary_function<T, T, T> {
    T operator() (const T& x, const T& y) const {
        return x - y;
    }
};

template <class T>
struct equal_to : public binary_function<T, T, bool> {
    bool operator() (const T& x, const T& y) const {
        return x == y;
    }
};

template <class T>
struct less : public binary_function<T, T, bool> {
    bool operator() (const T& x, const T& y) const {
        return x < y;
    }
};

上述代码均取自标准库,仿函数的struct均继承自奇特的基类,且在操作符重载内部并无注明。其特殊基类的源码为:

template <class Arg, class Result>
struct unary_function {
    typedef Arg argument_type;
    typedef Result result_type;
};

template <class Arg1, class Arg2, class Result>
struct binary_function {
    typedef Arg1 first_argument_type;
    typedef Arg2 second_argument_type;
    typedef Result result_type;
};

上述代码中的两个基类无数据,无函数,只有typedef,其大小应为0,在代码中对基类执行sizeof所得结果为1。标准库中有很多仿函数都继承奇特的基类。其作用在后续笔记中补充。

namespace命名空间

在软件开发过程中,大型软件的开发不同部门间可能会产生函数名称相同、数据名称相同的情况。可将一个部门所写的一段程序放入一个namespace中,可保证不同namespace相同名称的函数调用不冲突。

#include <iostream>
#include <memory>
namespace fa1c0n01 {
    void test_member_template() {
    ...
    }
}   //namespace1

#include <iostream>
#include <list>
namespace fa1c0n02 {
    template <typename T>
    using Lst = list<T,allocator<T>>;
    void test_template_template_param() {
    ...
    }
}

int main(int argc, const char * argv[]) {
    fa1c0n01::test_member_template();
    fa1c0n02::test_template_template_param();
    return EXIT_SUCCESS;
}

调用函数的全名为namespace::function_name(),namespace中可放namespace中的“全局变量”,“全局函数”,与其他的namespace中的变量和函数名称无关。