turingwy

关于vs2013的bug

#include <iostream>

#include <memory>

#include <initializer_list>

#include<string>

using namespace std;


class test {

int a;

int b;

public:

test(initializer_list<int> a) {


}


test(int a) {


}

private:

test(const test &a) {

cout << "123";

}


};

int main() {

initializer_list<int> b = { 12, 2, 3 };

test a = b;

}


c++11中引入了一个新的类initializer_list 这个类是一个模板类,他使得用户可以定义一个构造函数,它的参数是initializer_list类型,我们使用这个函数定义一个对象时就可以使用:

test a = {1,2,3};的方式

也可以使用test a({1,2,3}) 或者test a(initializer_list<int>({1,2,3}))

如果使用第一种方式,并不是使用了拷贝构造函数,而是编译器开的外挂。也就是说这种方式不管拷贝构造函数有没有都可以使用


但是如果想代码中那样,test a = b;

这就使用了拷贝构造函数,拷贝构造函数自动的用b生成了一个临时test变量,然后调用了拷贝构造。

根据这一点,如果我们把拷贝构造函数设为private,在G++中编译无法通过,但在vs2013中仍然可以通过。

c++ primer书中说到,编译器可以跳过拷贝构造而直接使用test a(b),但是不过怎样,必须保证拷贝构造函数是可用的。vs违反了这一点。


同样的例子 test接受一个int变量的构造函数,test a = 12在G++中无法通过,因为首先12被隐式转换为test,然后调用拷贝构造函数,而拷贝构造函数是private的,所以编译失败。但在vs中却可以通过,很明显,vs的编译器把test a = 12 变成了test a(12)

而G++没有。


C++中的拷贝构造函数

C++中对象由于既有在堆栈中又有在堆中,导致了类的构造函数种类繁多

而在java中在堆栈中只有对象的引用,所以使用起来非常简单,易于上手。


本文主要记录我在学习中对C++拷贝构造函数的认识。


在C++ Primer书中,作者把这些构造函数的章节放到后边,而先将了STL中的内容,而这里面又经常出现拷贝构造函数,所以我特地记录一下这类构造函数的要点。


在C++ Primer一书中,拷贝构造函数在使用的时候其实就像内置类型的拷贝初始化。

这里先提一下什么是内置类型的拷贝初始化

内置类型就是int,char,short,lang,lang lang,double,float,bool这几个,除了bool都是继承自C语言,C语言中对这些类型的初始化和赋值都使用一种方式——等号。例如 int a = 13;

这种方式在C++中就叫做拷贝初始化,在C++中不仅仅有这一种方式,还可以使用小括号,大括号,以及等号大括号,这些叫做直接初始化

还有一点有意思的,C++竟然可以直接使用int()来生成一个临时未命名的int变量,并初始化为0。


这是对内置类型的初始化,对对象的初始化就更有意思了

首先,可以使用普通的构造函数,这一点和java没有区别。

但是C++引入了一种拷贝构造函数,使用这种函数,我们可以实现像内置类型一样形式的初始化。

例如 

myclass A;

myclass B = A;


首先A是一个由默认构造函数生成的对象,而B是用拷贝构造函数生成的对象。

如果是学过Java的人看到这里,一定觉得这是B持有了A的引用,并不是生成了一个新的对象。其实不是,这是C++的一个语法特征,我们可以定义一个构造函数,它的参数只能是一个本类的引用,经常会直接把这个参数设成const,毕竟我们是对它进行拷贝,而不是修改它,关键的一点也是如果A是一个const对象,如果参数不是const的引用,这里就会报错。继续刚才,这个构造函数实现的功能就是复制一遍A。


这里有两个问题

1.我们没有使用这个构造函数啊,我们使用的是等号啊。

这就是C++的语法特征,如果我们没有使用等号,而是用括号传入A,这就和普通的构造函数没有区别了,这叫做直接初始化。C++遇见用等号初始化一个对象时就自动查找是否有这么一个拷贝构造函数。事实上这个构造函数基本上有

2.我们没有定义这个构造函数啊,怎么会使用了它?

C++自动帮用户实现了一个合成拷贝构造函数,这个函数的功能就是拷贝传入的A的全部成员变量,对于内置类型,使用拷贝初始化,对于对象,使用对象的拷贝构造函数。


现在就清楚了为什么我们可以有这样的举动了,那么像C++string类的拷贝构造函数为什么可以接受一个C语言风格的字符串?这里就是C++另外一个语法特性 叫做类的隐式转换。

当一个函数需要某个对象时,我们可以传入一个不是这种类型的对象,前提是这个类有一个构造函数的参数是这种类型。C++就会自动的通过这个对象和符合这个对象的构造函数生成一个临时的变量,通过这个临时变量传给函数。

像 string a = "helloworld"; 虽然“helloworld”不是string类型,但string有一个参数为const char*的构造函数,就使得这里的字面值自动转化为string类型,然后调用了a的拷贝构造函数。


拷贝构造函数的使用场合不仅仅在用等号初始化一个对象时,当一个函数的参数是一个非引用对象时,会自动对实参进行拷贝;当一个函数的返回值是一个非引用对象也会这样。