团队成员建议使用这样的初始化器:
return Demo{ *this };
优于:
return Demo(*this);
假设这样一个简单的类:
class Demo {
public:
int value1;
Demo(){}
Demo(Demo& demo) {
this->value1 = demo.value1;
}
Demo Clone() {
return Demo{ *this };
}
};
我承认之前没有看到{ *this }
语法,并且找不到足够好的解释,以便我理解这两个选项的不同之处。是否有性能优势,语法选择或更多内容?
答案 0 :(得分:16)
你的同事错过了“统一初始化”的技巧,知道时不需要输入类型名称。例如。在创建返回值时。 Clone
可以定义为:
Demo Clone() {
return {*this};
}
这将根据需要调用Demo
拷贝构造函数。无论你认为这是好还是坏,都取决于你。
在GOTW 1萨特州作为指导原则:
指南:更喜欢使用{}初始化,例如向量v = {1,2,3,4};或者自动v =向量{1,2,3,4} ;,因为它更一致,更正确,并且避免必须知道旧式陷阱。在单参数情况下,您更喜欢只看到=符号,例如int i = 42;和自动x =任何东西;省略括号是好的。 ......
特别是,使用大括号可以避免与以下内容混淆:
Demo d(); //function declaration, but looks like it might construct a Demo
Demo d{}; //constructs a Demo, as you'd expect
大括号语法将使用一个构造函数,该构造函数首先获取初始化列表(如果存在)。否则它将使用普通的构造函数。它还可以防止上面列出的烦恼解析的可能性。
使用复制初始化时也有不同的行为。用标准方式
Demo d = x;
如有必要,编译器可以选择将x
转换为Demo
,然后将转换后的r值移动/复制到w
。类似于Demo d(Demo(x));
的东西,意味着调用了多个构造函数。
Demo d = {x};
这相当于Demo d{x}
,并保证只调用一个构造函数。在上面的两个赋值中,都不能使用显式构造函数。
正如评论中所提到的,存在一些陷阱。使用initializer_list
并具有“正常”构造函数的类可能会导致混淆。
vector<int> v{5}; // vector containing one element of '5'
vector<int> v(5); // vector containing five elements.
答案 1 :(得分:5)
这只是调用复制构造函数的另一种语法(实际上,用于调用构造函数,将大括号中的内容作为参数,在本例中为复制构造函数)。
就我个人而言,我会说它比以前更糟,仅仅因为它做同样的事情......它只依赖于C ++ 11.所以它增加了依赖性而没有任何好处。但你的里程可能会有所不同你必须问你的同事。
答案 2 :(得分:4)
我必须承认我以前从未见过。
WikiPedia说这是关于C++11 initializer lists(搜索“统一初始化”):
C ++ 03在初始化类型时存在许多问题。有几种方法可以初始化类型,并且它们在互换时并不会产生相同的结果。例如,传统的构造函数语法看起来像一个函数声明,并且必须采取措施来确保编译器最令人头疼的解析规则不会将其误认为是这样。只能使用聚合初始值设定项初始化聚合和POD类型(使用SomeType var = {/ stuff /};)。
然后,他们有了这个例子,
BasicStruct var1{5, 3.2}; // C type struct, containing only POD
AltStruct var2{2, 4.3}; // C++ class, with constructors, not
// necessarily POD members
以下解释:
var1的初始化行为就像聚合初始化一样。也就是说,对象的每个数据成员将使用来自初始化列表的相应值进行复制初始化。必要时将使用隐式类型转换。如果不存在转换,或仅存在缩小转换,则程序格式错误。 var2的初始化调用构造函数。
对于提供初始化列表构造函数的情况,他们还有其他示例。
基于以上原因:对于普通的数据结构案例,我不知道是否有任何优势。对于C ++ 11类,使用{}语法可能有助于避免编译器认为您声明函数的那些讨厌的场景。也许这是你同事所指的优势?
答案 3 :(得分:2)
很抱歉这次讨论迟到了,但我想补充一些关于其他人没有提到的初始化类型的观点。
考虑:
struct foo {
foo(int) {}
};
foo f() {
// Suppose we have either:
//return 1; // #1
//return {1}; // #2
//return foo(1); // #3
//return foo{1}; // #4
}
然后,
#1
,#3
和#4
可能会调用复制/移动构造函数(如果未执行RVO),而#2
将不会调用复制/移动构造函数
请注意,最受欢迎的编译器确实执行RVO,因此,实际上,上述所有return
语句都是等效的。但是,即使执行了RVO,f
,#1
和#3
也必须可以使用复制/移动构造函数(#4
必须可以访问并且已定义但未删除)否则编译器/链接器将引发错误。
现在假设构造函数是显式的:
struct foo {
explicit foo(int) {}
};
然后,
#1
和#2
不进行编译,而#3
和#4
进行编译。
最后,如果构造函数是显式的,并且没有可用的复制/移动构造函数:
struct foo {
explicit foo(int) {}
foo(const foo&) = delete;
};
没有return
语句编译/链接。
答案 4 :(得分:1)
这称为list-initialization
。我们的想法是,在C ++ 11中,您将在整个板上进行统一的初始化,并避免在编译器可能认为您可能正在进行函数声明(也称为烦恼的解析)的模糊处理。一个小例子:
vec3 GetValue()
{
return {x, y, z}; // normally vec(x, y, z)
}
您希望避免列表初始化的一个原因是您的类采用初始化程序列表构造函数,该构造函数执行与您期望的不同的操作。