浅析C++的构造函数,拷贝构造函数和赋值运算符

  一.首先通过下面的测试程序,粗略的感知一下这三个函数分别在什么情况下被调用


#include <iostream>

using namespace std;

class T
{

public:
  T() { cout
<<"dafault constructor"<<endl;}
  T(
const T & t) {cout<<"copy constructor"<<endl;}
  void operator 
=(const T &t) { cout<<"assignment operator"<<endl;}
};

int main ( int argc
,char * argv[])
{
    T a;
    T b
=a;
    b
=a;

    
return 1;
}



程序运行结果为

    dafault constructor   //T a;
    copy constructor     // T b=a;
    assignment operator  // b=a;

从该测试程序我们可以知道,拷贝构造函数与赋值运算符虽然所完成的工作在概念上很相似——都是根据一个已有对象来对确定另一个对象的内容,然而二者有本质 的不同,前者用于初始化,而后者用于赋值操作。换言之,对于拷贝构造函数,当其被调用时,源对象已存在,而目标对象还未被创建;而对于赋值运算符,当其被 调用时,源对象和目标对象都已存在。


二. 下面我们需要明确如下几个概念:

1.什么是cv-qualified

这里的CV分别是const 以及volatile的缩写。用const,volatile以及const volatile之一作修饰的变量被成为cv-qualified ,否则该变量是cv-unqualified

2.direct-initilization

   "The initialization that occurs in new expressions , static_cast expressions , functional notation type conversions , and base and member initializers  is called direct-initialization and is equivalent to the form:
  
     T x(a);  

3. copy-initilization

    The initialization that occurs in argument passing, function return, throwing an exception , handlingan exception , and brace-enclosed initializer lists is called copy-initialization and is equivalent to the form:
   
    T x = a;

4 UDT:User Defined Type,即非C++内置类型


三.构造函数与拷贝构造函数

构造一个对象,有如下三种形式:

1、T a;
    这个没什么好说的,调用default ctor来构造a。

    不过要注意的是,要么T就一个ctor也没有,编译器合成default ctor,即T::T()
如果T有手动添加的其他形式的ctor,但是没有T::T(),则此语句报错,因为编译器不再为T合成default ctor

*注1,如果没有default ctor,但是有某个ctor的所有参数都有缺省值,则T a;也成立

2、 T a(v) ;

    这个语句显式用v作为参数调用T的某个最适合的可单参数调用的ctor来构造a。该语句形式被C++标准称为direct-initialization
    这里强调两点:
    一个是显式,这说明这种方式构造a的话可以调用被explicit声明的ctor。
    第二点是最适合,这是由overload rules决定的,而不是那么想当然,示例代码如下:
#include <iostream>

using namespace std;

class T
{
public:
  T(){cout
<<"dafault constructor"<<endl;}
  T(int){cout
<<"constructor for type  int"<<endl;}

  operator int(){cout
<<"converted to type int"<<endl;return 0;}
private:
  T(T
&){cout<<"copy constructor"<<endl;}
};

T foo() {
return T();}

int main(int argc
,char * argv[])
{
//  T a;

//  T b(a);      // 编译出错,最适合的T(T&)不可访问,为private


    T c(foo()); 
// 编译成功,foo()返回的临时对象是rvalue,不能绑定到non-const引用
                // 因此T(T&)不是由overload rules选定的最适合ctor
                // 但是rvalue可以调用一次自定义隐式转换cast到int然后调用T(int)构造c
                // 也就是说,overload rules选择了T(int)


   
return 1;
}


这段代码说明,即使类型相同,调用的也不一定是copy ctor

3、T a=v;

    该语句形式被C++标准称为copy-initialization,这个名称有很大的迷惑性。

    C++标准中对如何处理copy-initialization规定如下:

1)
   "If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered.The applicable constructors are enumerated,and the best one is chosen through overload resolution .The constructor so selected is called to initialize the object, with the initializer expression(s) as its argument(s). If no constructor applies,or the overload resolution is ambiguous, the initialization is ill-formed."

    总结以上引文的意思便是,如果copy-initialization中的v的cv-unqulified的类型为T
或是T的派生类,那么处理方式与direct-initialization几乎一样;唯一的区别在于,C++中只有direct-initialization中的T a(v)被视为显式调用ctor,其他情形均视为隐式调用ctor,也就是说,其他况下无法调用explicit性质的ctor。

2)
    "Otherwise , user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a
derived class thereof are enumerated , and the best one is chosen through
overload resolution . If the conversion cannot be done or is ambiguous, the initialization is ill-formed.
    The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation
is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; "

    总结以上引文的意思是:对于语句 T a=v;,当v的类型不是T及其选派生类时,从语义上需要先选用T的一个合适的构造函数(如果存在的话)从v构造出一个临时对象T,然后同direct- initilization的情形一样,调用合适的copy ctor来构造a。
    然而,标准说明,编译器在实现时允许选择消除拷贝过程,而直接在要构造的a之上构建中间临时对象。这里特别要强调的是,尽管优化是允许的,但必须
语义上 保证调用copy ctor的合法性。

下面是一段测试程序:

#include <iostream>

using namespace std;

class T
{
public:
  T(){cout
<<"default constructor"<<endl;}
  T(int i){cout
<<"constructor from int"<<endl;}
  operator int(){cout
<<"converted to int"<<endl; return 0;}
private:
  explicit T(T
& t){cout<<"explicit copy constructor"<<endl;}
};


int main ( int argc
,char *argv[])
{


    T a 
= T();       // direct-initialization,但等同于direct-initialization
                      //
 编译通过,相当于隐式调用T a(T());
                    // 而重载规则选用了T(int),即T a(static_cast<int>(T()));
                    // 如果T::T(int)声明为explicit,编译出错

   //  T b = 0;     //copy-initialization
                      // 编译出错,copy ctor不能访问,虽然该调用可能会被编译器优化,但是必须在语义上
                    // 保证其合法性。


    
return 1;
}

再看另一段测试代码,注意与上一段代码的区别:
#include <iostream>

using namespace std;

class T
{
public:
   T(int i){cout
<<"constructor from  int"<<endl; }
   explicit  T(
const T& t){cout<<"copy constructor"<<endl;}
};

void foo(T t){}


int main ( int argc
,char * argv[] )
{

// foo(T(0));    //相当于 T t=T(0),copy-initialization中的第一种情形
                   //编译失败,因为隐式调用无权调用explicit的ctor.

// foo(0);       //相当于T t=0,copy-initialization中的第二种情形
                  //编译失败,也是因为隐式调用无权调用explicit的ctor


  
return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值