C++中对象初始化的不同方式

63

想象一下这个类:

class Entity {
public:
    int x, y;
    
    Entity() : x(0), y(0) { }
    Entity(int x, int y) : x(x), y(y) { }
}

这里有多种我认为可以初始化该类的方式:

Entity ent1;                //Uses the default constructor, so x=0 and y=0
Entity ent2();              //Uses the default constructor, so x=0 and y=0 (Not sure)
Entity ent3(1, 2);          //Made constructor, so x=1 and y=2
Entity ent4 = Entity();     //Default constructor, so x=0 and y=0
Entity ent5 = Entity(2, 3); //Made constructor, so x=2 and y=3

我知道可以在堆内存上创建对象,但这不是我此时要寻求的。

我的问题是,初始化对象的这些方式有什么区别?

我不确定在什么情况下应该使用哪种方法。


19
Entity ent2(); 是一个函数声明。 - tkausl
2
不,这意味着根本没有“实体”,“ent2”是一个函数。 - tkausl
5
不是,这意味着你在声明一个名为 ent2 的函数,其返回值类型为 Entity - Arnav Borborah
2
你可以在C++11及以后的版本中使用{}进行统一初始化,可以查阅相关资料了解。你提供的例子是针对C++87的(可能MVP允许变量ent2,但编译器不一致)。 - Swift - Friday Pie
2
@JesperJuhl,那不是我的“个人”昵称。它基于Stroustroup的书籍第一版。本质上,他在1986-1987年描述的语言是C++87。因为没有其他描述。 - Swift - Friday Pie
显示剩余9条评论
1个回答

94

初始化的差异不仅在于其形式,还在于被初始化的实体类型。在这种情况下,它是一个具有定义好的默认构造函数和带参数构造函数的类类型对象。

Entity ent1;  

上述语句是默认初始化,导致调用类Entity的默认构造函数。
Entity ent2();

如果可能的话,编译器会将上述声明视为函数原型。 Entity 将是函数 ent2 的返回类型,该函数不带参数。这被称为最棘手的解析(MVP)之一,其存在导致出现了误导性的“聪明愚蠢规则”:“永远不要使用括号”。

在这样的语句中,与参数列表匹配的用户定义构造函数将为ent3对象调用:

Entity ent3(1, 2);    // calls Entity(int x, int y)

MVP 可以发挥作用的另一个案例是这样的:

Entity ent3_1(int(a), int(b));  // It's not what it looks like.

ent3_1不是一个变量。这个语句声明了一个具有两个int参数的函数。int(a)int a相同,是C语言和声明语法的遗留问题,它忽略了“额外”的括号。


Entity ent4 = Entity();

ent4 是 C++11 之前 ent2 案例的一个适当的版本。默认构造函数是作为值初始化的一部分被调用的。它的形式允许避免解决原则的歧义,从而使ent2ent3_1不正确。等号在这里不是赋值,因为没有operator= 的调用发生。它是声明语法的一部分,旨在标记初始化表达式。


Entity ent5 = Entity(2, 3);

ent5 是 ent3 情况的一个版本。用户定义的构造函数作为值初始化的一部分被调用。


你的问题标记为C++11,而C++11允许使用统一的初始化语法:

Entity ent12{};     // This is a legal alternative of ent2 case
Entity ent13{1, 2}; // A call to constructor or member initialization
Entity ent13{ int(a), int(b) }; // Not a function anymore
Entity ent14 = {};              // Not an assignment
Entity ent15 = Entity{2, 3};    // Not an assignment either!

请注意,统一初始化语法有一个警告。例如,这行代码:
std::vector<int> v(10); 

声明了一个包含10个元素的向量。但是这个{{one}}

std::vector<int> v{10};

声明一个类型为int且值为10的单元素向量。这是因为std::vector定义了以下签名的构造函数:

vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() );

如果您无法使用()而不触发MVP,也无法使用{}而不调用不需要的构造函数,则值初始化赋值语法可以解决此问题。
附录:必看CppCon 2018:Nicolai Josuttis“C ++中初始化的噩梦”

@CătălinaSîrbu ()或{}的区别在于提供参数与统一初始化。这可能导致不同行为的示例实际上已经在上面给出,但这是一个完全独立的问题。您不能使用任意形式的大括号内容来做到这一点。定义一个接受std::initializer_list<T>的构造函数是上述技巧的关键,因为初始化列表中类型为T的元素将默认绑定到该代理类。滥用这种方法可能会导致混乱。 - Swift - Friday Pie
1
重点是,至少在旧标准中,无论复制是否可以省略,复制构造函数必须存在且可访问,因为复制是语义的一部分。也就是说,编译器检查复制构造函数是否存在和可访问,只有在测试结果为正时才会继续省略对复制构造函数的调用。我认为他们已经改变/计划在最新标准中进行更改,但在C++11之前,我已经验证了这一点仍然适用。 - cmaster - reinstate monica
我创建了一个类,在其中删除了复制移动构造函数(使用C++11语法)。现在只有ent3可以工作,但是ent5会抛出编译错误。我不知道发生了什么,也不知道该搜索什么! - Shuvo Sarker
Entity* ent; 这段代码会做什么?不知何故,当我执行 Entity* ent; 时,没有创建新对象,而 Entity* ent = new Entity(); 则会创建一个新对象和相应的指针。第一个似乎可以解释为我只是创建了一个指针。但是 new Entity() 不应该创建一个新的 Enitity 对象而不是 Enitity*(指针)吗? - levio_sa
在这样的语句中,像@levio_sa这样的赋值操作符没有被执行(也就是说,没有赋值操作符),现代C++也保证不会发生复制。这是一个众所周知的悖论,即“=在这里不是赋值”。是的,new表达式分配内存并构造对象,表达式返回的值用于初始化指针。而指针是一个变量,由声明创建。根据变量的类型,它可以是对象或非对象,例如引用是一个变量但不是对象。我建议您观看这个视频:http://www.youtube.com/watch?v=7DTlWPgX6zs - Swift - Friday Pie
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接