是 VC2015 的 BUG 还是 C++ 的什么特性

C++语言 码拜 9年前 (2016-04-04) 1256次浏览
1.

#include <cstdio>
#include <type_traits>
#include <typeinfo>
#include <vector>
class AA
{
public:
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	std::vector<AA> a{ AA(10) };
}

Debug 版, 关闭优化的情况下, 输出:
construct use type int
后正常结束, 没看到 拷贝构造 的调用.
2.

#include <cstdio>
#include <type_traits>
#include <typeinfo>
#include <vector>
class AA
{
public:
	AA(const AA& a) {};
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	std::vector<AA> a{ AA(10) };
}

输出
construct use type int
construct use type class AA
后崩溃.
在提供了拷贝构造函数的情况下, 却没用使用提供的那一份, 而是选用了模板那一份,执行完后竟然还崩溃了…
是 VC2015 的 BUG 还是 C++ 的什么特性
3.

#include <cstdio>
#include <type_traits>
#include <typeinfo>
#include <vector>
class AA
{
public:
	AA(const AA& a) = default;
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	std::vector<AA> a{ AA(10) };
}

输出
construct use type int
construct use type class AA
后正常结束, 没有崩溃了.
4.

#include <cstdio>
#include <type_traits>
#include <typeinfo>
#include <vector>
class AA
{
public:
	AA(const AA& a) = delete;
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	std::vector<AA> a{ AA(10) };
}

编译报错:  error C2280: “AA::AA(const AA &)”: attempting to reference a deleted function
5.

#include <cstdio>
#include <type_traits>
#include <typeinfo>
#include <vector>
class AA
{
public:
	AA(AA&& a) {};
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	std::vector<AA> a{ AA(10) };
}

编译报错:
error C2280: “AA::AA(const AA &)”: attempting to reference a deleted function

在有 template <typename ImpInterface>  AA(ImpInterface&& impobj) 这种右值引用:

Quote: 引用:

2. vs2015经测试没问题,很明显调用移动构造而不是复制构造啊.

你测试没有崩溃?
是调用移动构造, 关键是他要崩溃,
而且在没有定义 AA(const AA& a) 的情况下为什么就又不调移动构造了?

移动和拷贝都有 …..
g++ 的:

$ ./a.out  | c++filt -t
construct use type int
construct use type AA
construct use type AA
copy construct

2

引用:
Quote: 引用:

2. vs2015经测试没问题,很明显调用移动构造而不是复制构造啊.

你测试没有崩溃?
是调用移动构造, 关键是他要崩溃,
而且在没有定义 AA(const AA& a) 的情况下为什么就又不调移动构造了?

没有崩溃….移动构造有语法规定的,只要编译器检测到不能够被再次引用:

这就是标准 c++。本人试了一下 vs2015 update2 没有问题,五个版本(debug)都能执行。你的要是崩溃了,那就升级吧。
主楼的五个例子,分成两类,123 具有复制构造函数,45 没有。原因是后面构造 vector 的时候,语意要求复制构造,所以 45 编译错误。中间层传递对象的时候,能 move 则 move,23 原因是存在自定义 copy ctor,所以没有默认的 move ctor,但是那个 template ctor 刚好能够生成合适的接口,所以就都去调用它了。1 的时候有默认 move ctor,template 也生成能用的接口,但重载解析倾向非模板,所以调用了默认的 move,把 template 实例化的踢出去了。
至于写法的话,以前叫 big 3,现在叫 big 5,{move,copy} x {ctor,op=} 以及 dtor 要提供都提供,要么都不写,别整个半吊子,自找麻烦。 对于啥都吃的 ctor,能加限制条件就加上,一个没有的话,裸奔也没关系,只要把 big 5 写齐了,重载解析自动帮你搞定。

是 VC2015 的 BUG 还是 C++ 的什么特性

2

引用:
Quote: 引用:

这就是标准 c++。本人试了一下 vs2015 update2 没有问题,五个版本(debug)都能执行。你的要是崩溃了,那就升级吧。
主楼的五个例子,分成两类,123 具有复制构造函数,45 没有。原因是后面构造 vector 的时候,语意要求复制构造,所以 45 编译错误。中间层传递对象的时候,能 move 则 move,23 原因是存在自定义 copy ctor,所以没有默认的 move ctor,但是那个 template ctor 刚好能够生成合适的接口,所以就都去调用它了。1 的时候有默认 move ctor,template 也生成能用的接口,但重载解析倾向非模板,所以调用了默认的 move,把 template 实例化的踢出去了。
至于写法的话,以前叫 big 3,现在叫 big 5,{move,copy} x {ctor,op=} 以及 dtor 要提供都提供,要么都不写,别整个半吊子,自找麻烦。 对于啥都吃的 ctor,能加限制条件就加上,一个没有的话,裸奔也没关系,只要把 big 5 写齐了,重载解析自动帮你搞定。

1. 模板既然可以实例化出一个 move 构造, 为什么不能实例化出一个 copy 构造函数呢?  T&& 这种模板参数是可以接受 const & 这种参数形式的呀:

template <typename T>
void  f(T&& t)
{
}
void test()
{
    const int a = 10;
    f((const int&)a);
}

可以正常编译通过.
2.  按照你的说法, 这个构造vector的代码 对多个重载的构造函数,选用的优先级为:
1. 直接的 move 构造
2. 模板实例化出来的 move 构造
3. 直接的 copy 构造
直接的 copy 构造排的优先级最低, 但是没有的话反而要出错?
而且对下面的代码:

#include <cstdio>
#include <type_traits>
#include <typeinfo>
#include <vector>
class AA
{
public:
	AA(AA&& a)
	{
		printf("move\n");
	}
	AA(const AA& a)
	{
		printf("copy\n");
	};
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	std::vector<AA> a{ AA(10) };
}

按照你的说法应该输出
construct use type int
move
吧?
但是运行的结果却是:
construct use type int
copy
这个怎么解释?

问题还挺长,分着回答吧。
1. 模板和复制,移动没有直接关系,模板就是模板,按照实例化的规则去推导形参,然后实例化后参加正常的重载解析,假如胜出了,就是它,假如失败了,就出局,什么时候都是这套固定的规则。实例化后的结果假如刚好匹配复制语意,那就好像实例化了一个具有复制功能的构造函数,假如刚好匹配移动语意,则好像生了一个具有移动功能的构造函数,但无论结果假如,都不是模板的本意,也不能替代复制或移动构造函数,即编译器仍会默认生成后两者。
2. 至于你期望 move,结果看到 copy 的情况,简而言之,move 优化掉了,copy 没有。本人拿你给出的例子解析一下,你就知道咋回事了。

class AA
{
public:
	AA(AA&& a)
	{
		printf("move\n");
	}
	AA(const AA& a)
	{
		printf("copy\n");
	};
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	std::vector<AA> a{ AA(10) };
}

vector 的构造这句,发生了以下这些事:
a. 首先构造一个 AA 的临时对象,用 template ctor,原因是没有其他的可以匹配。
b. 然后构造了一个数组 AA x[1],后者只有一个元素,该元素从 a. 中刚刚构造的临时对象构造,这里有三个 ctor 可以匹配,分别是 copy ctor, move ctor, 和 template <AA> ctor,原因是是临时对象重载解析偏向 move ctor,和 template ctor,原因是后两者都是 AA&&,又原因是 move ctor 是普通成员函数,而 template ctor 是经实例化生成的,所以选择 move ctor,这个是重载解析的结果,并且重载解析成功,然后优化进来后直接把 move ctor 对应的代码给删了,原因是标准允许这样对付 special member,即便你定义的 move 具有 observable effect。此处假如你把例子中的 move 改成 AA(AA const&&),上面同样的重载过程则会导致 template ctor 胜出,这时候你就能看到 “construct use type”,并且显然编译器没能把他的代码优化掉,原因是标准不允许,上述特款只适用于标准认定的 special member,这也是为啥你应该提供 big 5,而不要依赖 template ctor 去生成匹配的接口。
c. 然后构造了一个 initializer_list<AA> 对象,包含两个指针,分别指向 b. 中构造的数组的 begin, end。
d. 然后对 vector 全部构造函数进行重载解析,选择了 vector(initialize_list<AA>) 的重载。
e. 然后执行这个函数,里面经层层倒手后最终分配内存,构造对象,并且新对象是从 initializer_list 里面的对象构造的,即 b. 中数组里面的对象。原因是要构造 AA 对象,这时候再次执行 AA 构造函数的重载解析,过程相似于 b.,但关键的区别是此时的源对象不是右值了,而是左值,所以必须调用复制构造函数,然后在你的例子里,显然这样的复制构造没有优化掉。此处假如你的类没有可用的 copy ctor,则编译错误(主楼 45 就是这种情况),例如你 delete 了,或只定义了 move ctor,后者副作用之一是默认的 copy ctor 会消失。
e 中有一点特别说明一下,vector(initializer_list) 吃的参数是个右值,但经它手拿出来的那些数组元素已经是左值了(有点相似于 perfect forwarding,每次你都得显示的 forward 或 move,否则一个间接层就能把右值性抹除了,显然 vc 的 stl vector 实现没特殊处理这里,libstdc++ 也没有),这点导致了程序对 copy ctor 的要求,左值性也可以通过以下程序验证。

	AA(std::initializer_list<AA> il)
	{
		static_assert(std::is_lvalue_reference<decltype(*il.begin())>::value,"");
	}

2

引用:
Quote: 引用:
Quote: 引用:

这就是标准 c++。本人试了一下 vs2015 update2 没有问题,五个版本(debug)都能执行。你的要是崩溃了,那就升级吧。
主楼的五个例子,分成两类,123 具有复制构造函数,45 没有。原因是后面构造 vector 的时候,语意要求复制构造,所以 45 编译错误。中间层传递对象的时候,能 move 则 move,23 原因是存在自定义 copy ctor,所以没有默认的 move ctor,但是那个 template ctor 刚好能够生成合适的接口,所以就都去调用它了。1 的时候有默认 move ctor,template 也生成能用的接口,但重载解析倾向非模板,所以调用了默认的 move,把 template 实例化的踢出去了。
至于写法的话,以前叫 big 3,现在叫 big 5,{move,copy} x {ctor,op=} 以及 dtor 要提供都提供,要么都不写,别整个半吊子,自找麻烦。 对于啥都吃的 ctor,能加限制条件就加上,一个没有的话,裸奔也没关系,只要把 big 5 写齐了,重载解析自动帮你搞定。

1. 模板既然可以实例化出一个 move 构造, 为什么不能实例化出一个 copy 构造函数呢?  T&& 这种模板参数是可以接受 const & 这种参数形式的呀:

template <typename T>
void  f(T&& t)
{
}
void test()
{
    const int a = 10;
    f((const int&)a);
}

可以正常编译通过.
2.  按照你的说法, 这个构造vector的代码 对多个重载的构造函数,选用的优先级为:
1. 直接的 move 构造
2. 模板实例化出来的 move 构造
3. 直接的 copy 构造
直接的 copy 构造排的优先级最低, 但是没有的话反而要出错?
而且对下面的代码:

#include <cstdio>
#include <type_traits>
#include <typeinfo>
#include <vector>
class AA
{
public:
	AA(AA&& a)
	{
		printf("move\n");
	}
	AA(const AA& a)
	{
		printf("copy\n");
	};
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	std::vector<AA> a{ AA(10) };
}

按照你的说法应该输出
construct use type int
move
吧?
但是运行的结果却是:
construct use type int
copy
这个怎么解释?

问题还挺长,分着回答吧。
1. 模板和复制,移动没有直接关系,模板就是模板,按照实例化的规则去推导形参,然后实例化后参加正常的重载解析,假如胜出了,就是它,假如失败了,就出局,什么时候都是这套固定的规则。实例化后的结果假如刚好匹配复制语意,那就好像实例化了一个具有复制功能的构造函数,假如刚好匹配移动语意,则好像生了一个具有移动功能的构造函数,但无论结果假如,都不是模板的本意,也不能替代复制或移动构造函数,即编译器仍会默认生成后两者。
2. 至于你期望 move,结果看到 copy 的情况,简而言之,move 优化掉了,copy 没有。本人拿你给出的例子解析一下,你就知道咋回事了。

class AA
{
public:
	AA(AA&& a)
	{
		printf("move\n");
	}
	AA(const AA& a)
	{
		printf("copy\n");
	};
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	std::vector<AA> a{ AA(10) };
}

vector 的构造这句,发生了以下这些事:
a. 首先构造一个 AA 的临时对象,用 template ctor,原因是没有其他的可以匹配。
b. 然后构造了一个数组 AA x[1],后者只有一个元素,该元素从 a. 中刚刚构造的临时对象构造,这里有三个 ctor 可以匹配,分别是 copy ctor, move ctor, 和 template <AA> ctor,原因是是临时对象重载解析偏向 move ctor,和 template ctor,原因是后两者都是 AA&&,又原因是 move ctor 是普通成员函数,而 template ctor 是经实例化生成的,所以选择 move ctor,这个是重载解析的结果,并且重载解析成功,然后优化进来后直接把 move ctor 对应的代码给删了,原因是标准允许这样对付 special member,即便你定义的 move 具有 observable effect。此处假如你把例子中的 move 改成 AA(AA const&&),上面同样的重载过程则会导致 template ctor 胜出,这时候你就能看到 “construct use type”,并且显然编译器没能把他的代码优化掉,原因是标准不允许,上述特款只适用于标准认定的 special member,这也是为啥你应该提供 big 5,而不要依赖 template ctor 去生成匹配的接口。
c. 然后构造了一个 initializer_list<AA> 对象,包含两个指针,分别指向 b. 中构造的数组的 begin, end。
d. 然后对 vector 全部构造函数进行重载解析,选择了 vector(initialize_list<AA>) 的重载。
e. 然后执行这个函数,里面经层层倒手后最终分配内存,构造对象,并且新对象是从 initializer_list 里面的对象构造的,即 b. 中数组里面的对象。原因是要构造 AA 对象,这时候再次执行 AA 构造函数的重载解析,过程相似于 b.,但关键的区别是此时的源对象不是右值了,而是左值,所以必须调用复制构造函数,然后在你的例子里,显然这样的复制构造没有优化掉。此处假如你的类没有可用的 copy ctor,则编译错误(主楼 45 就是这种情况),例如你 delete 了,或只定义了 move ctor,后者副作用之一是默认的 copy ctor 会消失。
e 中有一点特别说明一下,vector(initializer_list) 吃的参数是个右值,但经它手拿出来的那些数组元素已经是左值了(有点相似于 perfect forwarding,每次你都得显示的 forward 或 move,否则一个间接层就能把右值性抹除了,显然 vc 的 stl vector 实现没特殊处理这里,libstdc++ 也没有),这点导致了程序对 copy ctor 的要求,左值性也可以通过以下程序验证。

	AA(std::initializer_list<AA> il)
	{
		static_assert(std::is_lvalue_reference<decltype(*il.begin())>::value,"");
	}

还得加一句,vector(initializer_list) 的复制构造行为是标准要求的,不是 vc 实现质量的问题。
所以一言以蔽之,主楼的例子出错是原因是写法不符合标准。

2

引用:

……
还得加一句,vector(initializer_list) 的复制构造行为是标准要求的,不是 vc 实现质量的问题。
所以一言以蔽之,主楼的例子出错是原因是写法不符合标准。

是 VC2015 的 BUG 还是 C++ 的什么特性是 VC2015 的 BUG 还是 C++ 的什么特性

2

加上析构函数再试试

2

路过学习了。

2

引用:
Quote: 引用:

主楼的五个例子,分成两类,123 具有复制构造函数,45 没有。原因是后面构造 vector 的时候,语意要求复制构造,所以 45 编译错误。中间层传递对象的时候,能 move 则 move,23 原因是存在自定义 copy ctor,所以没有默认的 move ctor,但是那个 template ctor 刚好能够生成合适的接口,所以就都去调用它了。1 的时候有默认 move ctor,template 也生成能用的接口,但重载解析倾向非模板,所以调用了默认的 move,把 template 实例化的踢出去了。

引用:

1. 模板既然可以实例化出一个 move 构造, 为什么不能实例化出一个 copy 构造函数呢?  T&& 这种模板参数是可以接受 const & 这种参数形式的呀:

引用:

问题还挺长,分着回答吧。
1. 模板和复制,移动没有直接关系,模板就是模板,按照实例化的规则去推导形参,然后实例化后参加正常的重载解析,假如胜出了,就是它,假如失败了,就出局,什么时候都是这套固定的规则。实例化后的结果假如刚好匹配复制语意,那就好像实例化了一个具有复制功能的构造函数,假如刚好匹配移动语意,则好像生了一个具有移动功能的构造函数,但无论结果假如,都不是模板的本意,也不能替代复制或移动构造函数,即编译器仍会默认生成后两者。

这个没有回答出本人的问题啊, 还是说本人的问题没描述清楚:
在代码 2 中,原因是提供了拷贝构造函数, 阻止了编译器生成默认的移动构造函数, 于是从模板实例化出了一份移动构造函数.
在代码 5 中,原因是提供了移动构造函数, 阻止了编译器生成默认的拷贝构造函数, 却不能从模板实例化出一份拷贝构造函数, 为什么不能? 你上面的回答中没看到那一条规则是阻止它从模板实例化拷贝构造函数的.

跟你说了,模阪实例化无法生成复制构造函数,c++ 语言要求复制构造函数是普通成员。因此 5 中编译器会默认生成一个 deleted 复制构造函数,后面重载解析的时候就挂了。

2

引用:
Quote: 引用:

2. 至于你期望 move,结果看到 copy 的情况,简而言之,move 优化掉了,copy 没有。本人拿你给出的例子解析一下,你就知道咋回事了。
vector 的构造这句,发生了以下这些事:
a. 首先构造一个 AA 的临时对象,用 template ctor,原因是没有其他的可以匹配。
b. 然后构造了一个数组 AA x[1],后者只有一个元素,该元素从 a. 中刚刚构造的临时对象构造,这里有三个 ctor 可以匹配,分别是 copy ctor, move ctor, 和 template <AA> ctor,原因是是临时对象重载解析偏向 move ctor,和 template ctor,原因是后两者都是 AA&&,又原因是 move ctor 是普通成员函数,而 template ctor 是经实例化生成的,所以选择 move ctor,这个是重载解析的结果,并且重载解析成功,然后优化进来后直接把 move ctor 对应的代码给删了,原因是标准允许这样对付 special member,即便你定义的 move 具有 observable effect。此处假如你把例子中的 move 改成 AA(AA const&&),上面同样的重载过程则会导致 template ctor 胜出,这时候你就能看到 “construct use type”,并且显然编译器没能把他的代码优化掉,原因是标准不允许,上述特款只适用于标准认定的 special member,这也是为啥你应该提供 big 5,而不要依赖 template ctor 去生成匹配的接口。

1. 这种优化方式是
A) 原因是本人定义的移动构造函数没有对对象产生任何作用, 而仅仅把这个函数调用优化掉
还是
B) 优化掉数组 AA x[1], 直接让  initializer_list<AA> 的指针指向 AA 临时对象所在的内存.
2. 假如优化方式是 B, 无论 AA 提供了什么样的构造方式不都是可以进行的吗? 为什么从模板实例化了移动构造函数就不行了呢.
3. 这种优化莫非不受编译选项中的 “禁止优化” 影响吗?
4. 哪里可以获得关于 “原因是标准允许这样对付”, “原因是标准不允许” 的资料或介绍

1. 都不是吧,应该是直接把临时对象构造在数组中。
2. 说了,标准不允许优化具有 observable effect 的函数,你那些 printf 就叫 observable effect,但这条有个例外,就是标准认定的特殊成员函数不受此限制,所以虽然你的 move/copy 都有 printf,但编译器还是想优化就优化。模板实例化生成的函数不叫特殊成员函数,所以享受不了特权,因此没法优化。
3. 有些优化 debug 下也会默认进行,不是啥你都能控制的,不过这个是实现细节了,标准不要求。
4. 标准标准标准,重要的事情说三遍。
搞来搞去,本人看你就是卡在认为模板能生成复制或移动构造函数。实际情况是语言专门禁止了这种情况。多读标准吧,别本人想象。

2

引用:
Quote: 引用:

至于写法的话,以前叫 big 3,现在叫 big 5,{move,copy} x {ctor,op=} 以及 dtor 要提供都提供,要么都不写,别整个半吊子,自找麻烦。 对于啥都吃的 ctor,能加限制条件就加上,一个没有的话,裸奔也没关系,只要把 big 5 写齐了,重载解析自动帮你搞定。

#include <cstdio>
#include <type_traits>
#include <typeinfo>
#include <vector>
class AA
{
public:
	AA(AA&& a)
	{
		printf("move\n");
	}
	AA(const AA& a)
	{
		printf("copy\n");
	};
	AA& operator=(AA&& a)
	{
		printf("move assigment\n");
		return *this;
	}
	AA& operator=(const AA& a)
	{
		printf("copy assigment\n");
		return *this;
	}
	virtual ~AA()
	{
		printf("destory\n");
	}
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	AA a1(10);
	std::vector<AA> a{ a1 };
}

输出:
construct use type int
construct use type class AA
copy
destory
destory
destory
假如模板没有限制, big5 齐了还是没用啊

你的复制构造函数缺一块吧,试试这个。

    AA(const AA& a)
    {
        printf("copy\n");
    };
    AA(AA& a)
    {
        printf("copy\n");
    };

或 explicit template ctor 也行。

2

引用:
Quote: 引用:

至于写法的话,以前叫 big 3,现在叫 big 5,{move,copy} x {ctor,op=} 以及 dtor 要提供都提供,要么都不写,别整个半吊子,自找麻烦。 对于啥都吃的 ctor,能加限制条件就加上,一个没有的话,裸奔也没关系,只要把 big 5 写齐了,重载解析自动帮你搞定。

#include <cstdio>
#include <type_traits>
#include <typeinfo>
#include <vector>
class AA
{
public:
	AA(AA&& a)
	{
		printf("move\n");
	}
	AA(const AA& a)
	{
		printf("copy\n");
	};
	AA& operator=(AA&& a)
	{
		printf("move assigment\n");
		return *this;
	}
	AA& operator=(const AA& a)
	{
		printf("copy assigment\n");
		return *this;
	}
	virtual ~AA()
	{
		printf("destory\n");
	}
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	AA a1(10);
	std::vector<AA> a{ a1 };
}

输出:
construct use type int
construct use type class AA
copy
destory
destory
destory
假如模板没有限制, big5 齐了还是没用啊

initializer_list 中有一些特别的规定,这里调用的AA(AA&)这样的拷贝构造,由于没实现,就用模板的。

2

标准规定
拷贝构造第一个参数有4种写法。X& ,const X&,volatile X& or const volatile X&,
重载决议
最优先匹配的是精确比配,之后是需要平凡转换的( T -> const T ,函数名->函数指针,数组->指针)
由于template <typename ImpInterface> AA(ImpInterface&& impobj)的存在,造成了,只有精确匹配的存在,忽略平凡转换。
那么 std::vector<AA> a{ a1 };中a1是非const的,那么  AA&版的拷贝构造优先 const AA&。 可以给a1加上const看看效果。

AA b{a1};
AA c = a1;
AA d(a1);

这种也全是调用 AA&版的构造函数,

2

巧了!
孔乙己先生说茴香豆的回字也有四中写法。
是 VC2015 的 BUG 还是 C++ 的什么特性

4

建议在mingw32编译器试试

2

引用:

好吧, 你对全部 “为什么” 的回答就是 “标准允许” 和 “标准不允许”.

本人也是醉了,c++ 标准定义c++语言,不用标准,用什么啊?
本人长篇大论解释了这么多,然后你说本人没解释“为什么”。

引用:

最后, 你的标准是 不允许模板生成复制和移动构造函数, 是吗?

是。
不是本人的标准,是国际标准。

引用:

但是在 代码 2 中 模板生成了移动构造函数.
在 26 楼的代码中(简称代码 6 吧), 模板确实生成了 AA(AA& a)  形式的复制构造函数, 对吧?

它们不叫复制构造函数,它们只是刚好能匹配复制构造的重载解析。
上面两条本人已经说了好几遍了,你要不理解它们的区别,本人也爱莫能助了。

引用:

再看下面的 代码 7

#include <cstdio>
#include <type_traits>
#include <typeinfo>
#include <vector>
class AA
{
public:
	AA(AA&& a)
	{
		printf("move\n");
	}
	AA(AA& a)
	{
		printf("copy\n");
	};
	AA& operator=(AA&& a)
	{
		printf("move assigment\n");
		return *this;
	}
	AA& operator=(AA& a)
	{
		printf("copy assigment\n");
		return *this;
	}
	virtual ~AA()
	{
		printf("destory\n");
	}
	template <typename ImpInterface>
	AA(ImpInterface&& impobj)
	{
		printf("construct use type %s\n", typeid(impobj).name());
	}
private:
};
void  test_language()
{
	std::vector<AA> a{ AA(10) };
}

输出为:
construct use type int
construct use type class AA
destory
destory
编译器是用模板来生成了形如 AA(const AA&) 的拷贝构造函数, 对吧?

最后再解释一次,在你全部的例子里,编译器都是一样的逻辑。
假如需要生成构造函数,就生成,假如不需要,就使用用户提供的。
同时根据调用参数实例化函数模板,然后进行重载解析,选定谁就是谁。
至于 big 5/7/11,本来也没有标准的定义。
假如理解成,big 5 concepts,那就没什么问题,只不过 copy ctor 这个概念下能够至多对应 4 个函数。
假如你非得理解成函数,那就 big n,反正你本人统一就行了,who cares。

2

引用:
Quote: 引用:

它们不叫复制构造函数,它们只是刚好能匹配复制构造的重载解析。
上面两条本人已经说了好几遍了,你要不理解它们的区别,本人也爱莫能助了。
最后再解释一次,在你全部的例子里,编译器都是一样的逻辑。
假如需要生成构造函数,就生成,假如不需要,就使用用户提供的。
同时根据调用参数实例化函数模板,然后进行重载解析,选定谁就是谁。

好吧, 你叫他 “匹配复制构造的重载解析” 就叫 “匹配复制构造的重载解析” 吧.
本人不关心他叫什么, 也不关心他和复制构造函数的区别…
本人的问题在于编译器什么时候会用模板来生成这个 “匹配复制构造的重载解析”?
为什么在 代码 5 中编译器不用模板来生成一个形如 AA::AA(const AA &) 的 “匹配复制构造的重载解析” 而在代码7 中会生成?
另外, 一点哲学上的个人观点, 标准不是问题的终极答案, 制定标准的时候肯定是经过各种考量的, 所以,一样可以问”标准为什么允许?” 的. 假如不用整天把标准挂在嘴边, 可以简略的问成 “为什么允许?”, 这时候期待的答案通常不是 “标准允许.”

标准规定:有移动构造的话,就不会自动生成拷贝构造函数了。至于原因只能去标准草案的邮件列表里面找了。

2

引用:
Quote: 引用:

标准规定:有移动构造的话,就不会自动生成拷贝构造函数了。至于原因只能去标准草案的邮件列表里面找了。

你是说, 有移动构造的话, 就不会从模板实例化出拷贝构造函数吗?  本人这里问的是模板实例化, 没问生成默认的拷贝构造函数哈.
假如你是上面的意思, 那么在 代码7 中, 有移动构造, 仍然从模板实例化出了一份拷贝构造函数.

本人理解错误,本人说的是没有模板的情况下.
std::vector<AA> a{ AA(10) };
这里的AA(10)是右值,自然无法使用 AA(AA&)这个版本的拷贝构造。
需要AA(const AA&)版的,没有精确匹配,就使用模板版的。

2

引用:
Quote: 引用:
Quote: 引用:

标准规定:有移动构造的话,就不会自动生成拷贝构造函数了。至于原因只能去标准草案的邮件列表里面找了。

你是说, 有移动构造的话, 就不会从模板实例化出拷贝构造函数吗?  本人这里问的是模板实例化, 没问生成默认的拷贝构造函数哈.
假如你是上面的意思, 那么在 代码7 中, 有移动构造, 仍然从模板实例化出了一份拷贝构造函数.

本人理解错误,本人说的是没有模板的情况下.
std::vector<AA> a{ AA(10) };
这里的AA(10)是右值,自然无法使用 AA(AA&)这个版本的拷贝构造。
需要AA(const AA&)版的,没有精确匹配,就使用模板版的。

更正一下
标准规定,有移动构造的话,编译器就不会生成拷贝构造函数。
跟有没有模板重载没有关系,但模板可以实例化出对应的拷贝构造函数,和编译器生成的不一样。

2

高手们  厉害  本人是来学习的

2

引用:

本人的问题是,模板什么时候才实例化出对应的拷贝构造函数, 什么时候又不实例化出对应的拷贝构造函数

规则比较复杂,本人也是查了半天才清楚,最重要的一点就是。
模板函数不能阻止编译器生成构造函数、拷贝构造、移动构造。
剩下的就是模板 重载决议。
根据标准规定,AA类生成的拷贝构造参数为  AA(const &&) 移动构造为 AA(AA&&);
1 AA(10) 不符合编译器生成的构造,于是就用模板构造对象,由于AA(10)是右值,那么构造initializer_list<AA>匹配 生成的移动构造函数,vector内使用拷贝构造
2  由于定义了拷贝构造,那么就不会生成移动构造,移动构造由模板实例化。
3  同2 ,定义了拷贝构造,实现由编译器生成
4  基本同2,存在一个删除的拷贝构造。模板不能生成delete的函数,否则delete关键字无意义,达不到目的。
5 只定义了移动构造,没定义默认的拷贝构造的话,相当于 AA(const AA&)=delete; 模板不能生成拷贝构造。于是编译错误。
7 已经解释过了,右值不能匹配 AA(AA&),所以用的是模板生成的AA(const AA&);

2

引用:

你的结论是, 只要这个类有任意一种参数形式的拷贝构造(不管是默认生成的, 还是用户定义的), 模板就会实例化出其余参数形式的拷贝构造和移动构造. 假如这个类没有任何一种拷贝构造, 模板就不能实例化出拷贝构造和移动构造.
是这样的吗?

不是
编译器先根据用户定义的构造函数情况,生成、不生成或删除 对应的拷贝构造、移动函数。
之后模板实例化,进行重载决议。

引用:

只定义了拷贝构造的时候呢? 能否也相当于 AA(AA&&) = delete; 了呢?

不相当于AA(AA&&&)=delete,原因是没有移动的时候,使用右值构造,就会直接调用拷贝构造,而不是的编译错误。

2

引用:

前面都不管他, 现在只管这一个点 “之后模板实例化”
这个模板实例化的时候能否实例化出某种形式的拷贝构造函数是根据什么来决定的?  既然你已经否认了是根据之前的操作结果有某一种形式的拷贝构造函数, 那根据的是什么?

标准说是不具备构造函数签名。
A member function template is never instantiated to produce such a constructor signature.
和其他构造函数不是同等地位。
可以认为是构造函数,但不有完全是,是特例。

2

引用:
Quote: 引用:
Quote: 引用:

前面都不管他, 现在只管这一个点 “之后模板实例化”
这个模板实例化的时候能否实例化出某种形式的拷贝构造函数是根据什么来决定的?  既然你已经否认了是根据之前的操作结果有某一种形式的拷贝构造函数, 那根据的是什么?

标准说是不具备构造函数签名。
A member function template is never instantiated to produce such a constructor signature.
和其他构造函数不是同等地位。
可以认为是构造函数,但不有完全是,是特例。

这描述的是实例化出来的那个函数的性质, 也不是在描述什么条件下去实例化呀.

不管有没有模板 编译器都会自动生成这些拷贝构造和移动构造。
模板函数只是在调用的时候才推导模板参数,实例化模板的。

2

引用:

也就是说, 只要进入了调用阶段, 模板进行实例化构造函数就是无条件的了?
而要进入调用阶段, 只要这个类的构造函数没有声明为 delete 就行? 还是有其它条件才能让编译过程进入到调用阶段?

这个问题分成2个问题理解就容易了
编译器 生成的构造、移动函数或删除,不牵扯调用,纯粹根据 类的声明就能够有明确结果。但知道编译器生成了那些函数,函数签名是什么。
调用时模板实例化后,和普通函数、编译器自动生成的函数 进行重载决议。

2

引用:
Quote: 引用:

前面都不管他, 现在只管这一个点 “之后模板实例化”
这个模板实例化的时候能否实例化出某种形式的拷贝构造函数是根据什么来决定的?  既然你已经否认了是根据之前的操作结果有某一种形式的拷贝构造函数, 那根据的是什么?

标准说是不具备构造函数签名。
A member function template is never instantiated to produce such a constructor signature.
和其他构造函数不是同等地位。
可以认为是构造函数,但不有完全是,是特例。

这句说的应该不是拷贝/移动构造。
它说的模板实例化不能生成如下的签名: A(A a);
这个构造签名是不合法的。
拷贝构造是 A(A& a);

2

拷贝构造/移动构造的概念在决定重载决策(overload resolution)时是没有用到的。
在这一阶段,全部能进入候选集的函数是一视同仁的。
全部的构造函数,包括构造函数模板的实例化结果,总是同时进入候选集的。

210

引用:
Quote: 引用:
Quote: 引用:

也就是说, 只要进入了调用阶段, 模板进行实例化构造函数就是无条件的了?
而要进入调用阶段, 只要这个类的构造函数没有声明为 delete 就行? 还是有其它条件才能让编译过程进入到调用阶段?

这个问题分成2个问题理解就容易了
编译器 生成的构造、移动函数或删除,不牵扯调用,纯粹根据 类的声明就能够有明确结果。但知道编译器生成了那些函数,函数签名是什么。
调用时模板实例化后,和普通函数、编译器自动生成的函数 进行重载决议。

那里的意思是代码 4 报错是在进入第三阶段的时候报错的吗?
像这样:
第一阶段,  delete 并不是让编译器不生成构造函数, 而是生成一个有 delete 标签的构造函数.
进入第二阶段, 模板照样实例一个 template <AA>  AA(const AA& ) 出来.
进入第三阶段, 重载决议阶段, 编译器生成的构造函数获胜, 但是发现这个函数带有 delete 标签, 于是产生编译错误.

是这么回事


CodeBye 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明是 VC2015 的 BUG 还是 C++ 的什么特性
喜欢 (0)
[1034331897@qq.com]
分享 (0)