C++11类(1) 基础技巧 Class Basic

来源:互联网 发布:康师傅 统一 知乎 编辑:程序博客网 时间:2024/04/25 09:55

Reference:The C++ Programming Language 4th edition (Bjarne Stroustrup)

1.explicit构造函数

单参数的构造函数会默认作为参数类型到此类类型隐式类型转换,在许多情况下这种隐式的类型转换很有可能导致错误

幸运的是,我们可以用explicit关键字显示声明构造函数(并不必须是单参数)不用作隐式的类型转换

struct X {     explicit X();     explicit X(int,int);};X x1 = {};       // error : implicitX x2 = {1,2};    // error : implicitX x3 {};         // OK: explicitX x4 {1,2};      // OK: explicitint f(X);int i1 = f({});     // error : implicitint i2 = f({1,2});  // error : implicitint i3 = f(X{});    // OK: explicitint i4 = f(X{1,2}); // OK: explicit

Good Practice: 默认将单参数的构造函数声明为explicit,除非有强力的需求不这么做。


2. 类内初始化

当存在多个构造函数时, 构造函数的默认参数可能会造成冗余,此时可以对数据成员使用初始化设定项(initializer)

class Date {int d {today.d};int m {today.m};int y {today.y};public:Date(int, int, int);   // day, month, yearDate(int, int);        // day, month, today’s yearDate(int);            // day, today’s month and yearDate();               // default Date: todayDate(const char∗);   // date in string representation// ...


3. 类内定义函数

类内定义的函数会被认为是内联函数(inline),通常小型的,使用频率高,几乎不会修改的函数会被定义在类内。

class Date {public:    void add_month(int n) { m+=n; } // increment the Date’s m// ...private:    int d, m, y;};

4. 可变性(Mutability)

(1) 常成员函数

在函数的声明和定义中都要在参数列表后加const,即const是函数类型的一部分

常对象只能调用常成员函数,非常(non-const)对象可以调用常成员函数和非常成员函数

void Date::add_year(int n); void f(Date& d, const Date& cd){    int i = d.year();             // OK    d.add_year(1);                // OK    int j = cd.year();            // OK    cd.add_year(1);               // error : cannot change value of a const Date}

(2) 物理不变性和逻辑不变性(physical and logical constness)

Logical constness:  to a user, the function appears not to change the state of its object, but some detail that the user cannot directly observe is updated.

逻辑不变性:对于用户来说,一个函数似乎没有修改其对象的状态,但一些用户不能直接观察的细节得到了更新。

class Date {public:    // ...    string string_rep() const; // string representationprivate:    bool cache_valid;    string cache;    void compute_cache_value(); // fill cache    // ...};
例如Date内储存的年月日是整形,现在string_rep()要返回字符串格式的日期,重复的将字符串转化为日期可能是一个昂贵的操作,所以一个可行的解决方案是用cache储存字符串格式的日期,当调用string_rep()时返回cahce。从用户的角度,string_rep()并没有改变Date数据,所以它明显应该是const函数,但cache和cache_valid却有时会改变,这个问题可以野蛮的用const_cast解决,但下节将介绍一个更优雅的方法。

(3) mutable

我们可以把类的成员声明为mutable,这意味着const成员函数也能修改他们。

class Date {public:    // ...    string string_rep() const;          // string representationprivate:    mutable bool cache_valid;    mutable string cache;    void compute_cache_value() const;   // fill (mutable) cache    // ...};
于是现在string_rep的实现就显而易见了

string Date::string_rep() const{    if (!cache_valid) {        compute_cache_value();        cache_valid = true;    }    return cache;}
(4) 间接可变性

声明mutable变量适用于小对象里的小部分数据,当情况很复杂时,更好的办法是把这些数据分离到另外的对象,进行间接存取
上面的例子将变为

struct cache {    bool valid;    string rep;};class Date {public:    // ...    string string_rep() const; // string representationprivate:    cache∗ c;                         // initialize inconstr uctor    void compute_cache_value() const;  // fill what cache refers to// ...};string Date::string_rep() const{    if (!c−>valid) {        compute_cache_value();        c−>valid = true;    }    return c−>rep;} 

const并不会作用于通过指针和引用存取的成员对象,即仅指针的指向不变,指针指向的内容可变。


5. 自引用(Self-Reference)

对于一组相关的更新操作,返回被更新对象的引用往往非常有用,因为这使各个操作可以连接起来。

void f(Date& d){    // ...    d.add_day(1).add_month(1).add_year(1);    // ...}
在所有非static的成员函数中,关键字this指向调用此成员函数的对象。对于类X,this的类型是X*。不过一般认为this是右值,即它不能被取址或者赋值。在const成员函数中,this的类型是const X*。this的绝大部分使用时隐式的,所有非static成员函数依赖于隐式的使用this来获取与其对应的对象(默认前缀this->)。

一个常见的显示使用this的例子是链表操作:

struct Link {    Link∗ pre;    Link∗ suc;    int data;    Link∗ insert(int x) // inser t x before this    {        return pre = new Link{pre, this, x};    }    void remove() // remove and destroy this    {        if (pre) pre−>suc = suc;        if (suc) suc−>pre = pre;        delete this;    }    // ...};

6. 静态成员

 静态成员变量:A variable that is part of a class, yet is not part of an object of that class, is called a static member.

静态成员函数:A function that needs access to members of a class, yet doesn’t need to be invoked for a particular object, is called a static member function.

上文的类内初始化依赖于全局变量today,可能导致Date在上下文不同时无法使用。

下面的默认初始化不依赖于全局变量:

class Date {    int d, m, y;    static Date default_date;public:    Date(int dd =0, int mm =0, int yy =0);    // ...    static void set_default(int dd, int mm, int yy); // set default_date to Date(dd,mm,yy)};// We can now define the Date constructor to use default_date like this:Date::Date(int dd, int mm, int yy){    d = dd ? dd  :default_date.d;    m = mm ? mm : default_date.m;    y = yy ? yy : default_date.y;    // ... check that the Date is valid ...}

静态成员可以像其他成员一样被引用/调用,而且还可以在不提及对象的情况下被调用:

void f(){    Date::set_default(4,5,1945); // call Date’s static member set_default()}
静态成员在使用前必须被定义,包括成员变量:

Date Date::default_date {16,12,1770}; // definition of Date::default_datevoid Date::set_default(int d, int m, int y) // definition of Date::set_default{    default_date = {d,m,y}; // assign new value to default_date}
注意到此时Date{ }成为了Date::default_date的值的记号,所以我们不需要用另外的函数来读取默认值,当没有歧义是,仅仅用{ }就足够了:

void f1(Date);void f2(Date);void f2(int);void g(){    f1({}); // OK: equivalent to f1(Date{})    f2({}): // error : ambiguous: f2(int) or f2(Date)?    f2(Date{}); // OK}

在多线程程序中,static成员数据需要用锁或者某种存取规则来避免竞争条件(Race condition)。


7. 成员类型(Member Types)

类型或者类型别名可以作为类的成员:

template<typename T>class Tree {    using value_type = T;                  // member alias    enum Policy { rb, splay, treeps };     // member enum    class Node {                           // member class        Node∗ right;        Node∗ left;        value_type value;    public:        void f(Tree∗);    };    Node∗ top;public:    void g(const T&);    // ...};
成员类可以使用外围类的static成员和类型,但仅当为其提供一个外部类的对象时才能使用外围类的非静态成员,即内部类有权存取外围类的成员,包括私有成员,但它并不了解当前的外围类对象。
template<typename T>void Tree::Node::f(Tree∗ p){    top = right;                 // error : no object of type Tree specified    p−>top = right;             // OK    value_type v = left−>value; // OK: value_type is not associated with an object}
但外围类对内部类没有特殊的存取权限:
template<typename T>void Tree::g(Tree::Node∗ p){    value_type val = right−>value;   // error : no object of type Tree::Node    value_type v = p−>right−>value; // error : Node::r  ight is private    p−>f(this);                      // OK}

成员类更多是一种概念上的便利而不是一个重要的基础特性

成员别名非常重要,因为它是泛型编程中关联类型的根基

成员枚举常常是枚举类的替代,它避免了枚举名污染外围域
0 0
原创粉丝点击