C++

explicit构造函数

考虑一个类Date:

class Date{
  int d,m,y;
  //...
};

void my_fact(Date d);

void f()
{
  Date d{15};  //似乎合理:x变为{15,today.m,today.y}
  d = 15;    //含混
  my_fact(15);  //含混
  //...
}    

这最多是一段含混代码,数据15和Date之间并没有清晰的逻辑关联。

    但是,我们可以指明构造函数不能用作隐式类型转换。如果构造函数的声明带有关键字explicit,它只能用于初始化和显示类型转换。例如:

class Date{
  int d, m, y;
public:
  explicit Date(int dd = 0,int mm=0,int yy = 0);
  //...
};

Date d1{15};    //OK:被看作显式类型转换
Dated2 = Date{15};    //OK:显式类型转换
Date d3 = {15};    //错误: = 方式的初始化不能进行隐式类型转换
Date d4 = 15;    //错误: = 方式的初始化不能进行隐式类型转换

void f()
{
  my_fact(15);    //错误:参数传递不能进行隐式类型转换
  my_fact({15});  //错误:参数传递不能进行隐式类型转换
  my_fact(Date{15});  //OK:显式类型转换
}

用 = 进行初始化可以看做 拷贝初始化,一般而言,初始化器的副本会被方式待初始化的对象。但是,如果初始化器是一个右值,这种拷贝可能被优化掉(取消),而采用移动操作,省略 = 会将初始化变为显式初始化。显式初始化也称为 直接初始化。

  默认情况下,应该将单参数的构造函数生命为explicit。除非有更好的理由,否则应该按照这种默认的方式去做。如果定义隐士构造函数,最好写下原因,否则代码的维护者可能怀疑你疏忽了,或者不懂这一原则。

  如果一个构造函数声明为explicit且定义在类外,则在定义中不能重复explicit:

class Date{
  int d,m,y;
public:
  explicit Date(int dd);
//....
};

Date::Date(int dd){/*...*/}    //OK
explicit Date::Date(int dd){/*...*/}  //错误

大多数explicit起重要作用的构造函数都接收单一参数。但是,explicit也可以用于五参或多个参数的构造函数。例如:

struct X{
  explicit X();
  explicit X(int, int);
};

X x1 = {};    //错误:隐式的
X x2 = {1,2};    //错误:隐式的

X x3{};          //OK:显式的
X x4{1,2};    //OK:显式的

int f(X);
int i1 = f({});    //错误:隐式的
int i2 = f({1,2});     //错误:隐式的

int i3 = f(X{});    //OK:显式的
int i4 = f(X{1,2});  //OK:显式的

More:参考链接

mutable:

在const对象中,一个定义为mutable的成员可以被修改:

class Date{
public:
    //.....
    string string_rep() const;
private:
    mutable bool cache_valid;
    mutable string cache;
    void compute_cache_value() const;
    //..... 
};

string Date::Date string_rep() const
{
    if(cache_valid){
        compute_cache_value();
        cache_valid = true;    
    }
    return cache;
}

此种情况下,string_rep()即可用于const对象,也可用于非const对象。例如:

void f(Date d,const Date cd)
{
    string s1 = d.string_rep();
    string s2 = cd.string_rep();    //OK
}

this 自引用:

定义状态更新函数ad_year(),add_month() 和 add_day()没有返回值。对这样一组相关的更新函数,通常有用的技术是令它返回已更新对象的引用。例如:

void f(Date& d)
{
  //...
  d.add_year(1).add_month(1).add_year(1);
  //...
}
//为此,必须将每个函数都声明为返回一个Date引用:
class Date{
  //...
  Date& add_year(int n);  //增加n年
  Date& add_month(int n);  //增加n个月
  Date& add_day(int n);    //增加n天
};

每个(非static)成员函数都知道是哪个对象调用的它,并能显式引用这个对象。例如:

Date& Date::add_year(int n)
{
  if(d == 29 && m == 2 && !leapyear(y + n))  //小心2月29
  {
     d = 1;
     m = 3;
  }
  y += n;
  return *this;
}

表达式 *this 引用的就是调用此成员函数的对象。注意:this是一个右值,无法获得this的地址或对其赋值。this的使用大多是隐式的。this的显式应用一种体现是链表操作。例如:

struct Link{
  Link* pre;
  Link* suc;
  int data;

  Link* insert(int x)    //在this之前插入x
  {
     return pre = new Link(pre,this,x);
  }  
   void remove()   //删除并销毁this
  {
      if(pre) pre->suc = suc;
      if(suc) suc->pre = pre;
      delete this;
  } 
};

另外,从一个派生类模板访问基类的成员也要显式使用this。

显式初始化:

C ++支持两种显式初始化形式。

在括号中提供初始化程序列表:

String sFileName(“FILE.DAT”);

括号列表中的项目被认为是类构造函数的参数。这种初始化形式使得能够初始化具有多个值的对象,并且还可以与新的运算符一起使用。例如:

Rect * pRect = new Rect(10152497);

使用等号初始化语法提供单个初始化器。例如:

String sFileName =“FILE.DAT”;
  • 尽管上述示例的工作方式与第一个列表项中的String所示示例相同,但该语法不适用于在自由存储上分配的对象。

    等号右侧的单个表达式作为类的复制构造函数的参数; 因此,它必须是可以转换为类类型的类型。

    请注意,由于初始化上下文中的等号(=)与赋值运算符不同,因此重载operator =对初始化没有影响。

等号初始化语法与函数式语法不同,即使生成的代码在大多数情况下相同。不同的是,当使用等号语法时,编译器必须像以下事件序列一样运行:

  • 创建与正在初始化的对象相同类型的临时对象

  • 将临时对象复制到对象。

原文地址:https://www.cnblogs.com/gardenofhu/p/7596830.html