关于复制构造函数与赋值操作符,以及临时对象绑定

在C++中,表达式 MyClass obj3 = MyClass(20); 实际上涉及到了复制构造函数(copy constructor)和复制初始化(copy initialization),而不是赋值运算符。尽管它看起来可能像是一个赋值操作,但实际上这里发生的是对象的构造和初始化。

让我们详细分析一下这个表达式:

MyClass(20) 首先调用 MyClass 的构造函数,传入参数 20 来创建一个临时对象。这个临时对象在表达式结束时通常会被销毁(除非它被以某种方式延长了生命周期,比如被绑定到一个常量引用上,但在这个例子中并没有发生)。

然后,这个临时对象被用来初始化 obj3。由于 obj3 是一个新创建的对象,它的初始化是通过复制构造函数完成的。复制构造函数的参数是一个对 MyClass 类型的常量引用,它在这里被绑定到了临时对象上。

复制构造函数执行后,obj3 拥有了与临时对象相同的值(在这个例子中是 value_ 成员变量的值),而临时对象随后被销毁。

现在,关于这与赋值运算符的区别:

赋值运算符(=)用于已经存在的对象。它将右侧表达式的值赋给左侧已经构造好的对象。例如,MyClass obj1(10); MyClass obj2; obj2 = obj1; 在这里,obj2 首先被默认构造,然后通过赋值运算符被赋予 obj1 的值。

复制初始化(如 MyClass obj3 = MyClass(20);)则用于新对象的构造。它直接使用右侧表达式的值来构造左侧的对象。在这个过程中,复制构造函数被调用。

尽管在某些情况下,复制初始化和赋值可能看起来有相似的效果(比如都是将一个对象的状态复制到另一个对象),但它们发生的上下文和时机是不同的。复制初始化发生在对象构造时,而赋值则发生在对象已经存在之后。

另外,值得注意的是,从C++11开始,编译器可能会优化掉不必要的临时对象和复制操作,这被称为复制省略(copy elision)或返回值优化(RVO)。在上面的例子中,如果编译器能够确定临时对象不会被其他任何地方使用,它可能会直接构造 obj3 而不需要创建一个临时的 MyClass(20) 对象。然而,即使发生了这种优化,从语义上讲,表达式仍然被视为复制初始化。

当然,我们可以继续通过一些具体的编程示例来说明临时对象绑定(temporary object binding)的概念。在C++中,临时对象绑定通常涉及到右值引用(rvalue references)和移动语义(move semantics)。以下是一些进一步的例子:
例子1:函数参数中的临时对象绑定

假设我们有一个简单的类MyClass,它有一个接受右值引用的构造函数和一个接受右值引用的成员函数。

cpp

#include
#include // for std::move

class MyClass {
public:
MyClass() { std::cout << “Default constructor\n”; }
MyClass(const MyClass&) { std::cout << “Copy constructor\n”; }
MyClass(MyClass&&) noexcept { std::cout << “Move constructor\n”; }
MyClass& operator=(const MyClass&) { std::cout << “Copy assignment\n”; return *this; }
MyClass& operator=(MyClass&&) noexcept { std::cout << “Move assignment\n”; return *this; }
~MyClass() { std::cout << “Destructor\n”; }

void doSomething(MyClass&& other) {
    std::cout << "doSomething with rvalue reference\n";
    // 这里可以对other进行操作,它是通过右值引用传递的临时对象
}

};

int main() {
MyClass obj1;
MyClass obj2 = std::move(obj1); // 使用移动语义

MyClass().doSomething(std::move(obj2));  // 临时对象绑定到doSomething的参数

return 0;

}

在这个例子中,MyClass().doSomething(std::move(obj2));创建了一个MyClass的临时对象,并将其绑定到doSomething函数的右值引用参数other上。注意,这里还使用了std::move来显式地将obj2转换为右值引用,以便利用移动语义。
例子2:返回临时对象的函数

考虑一个函数返回一个类的实例,这个函数可能会创建一个临时对象并返回它。

cpp

#include
#include // for std::move

class MyClass {
public:
MyClass() { std::cout << “Default constructor\n”; }
MyClass(const MyClass&) { std::cout << “Copy constructor\n”; }
MyClass(MyClass&&) noexcept { std::cout << “Move constructor\n”; }
MyClass& operator=(const MyClass&) { std::cout << “Copy assignment\n”; return *this; }
MyClass& operator=(MyClass&&) noexcept { std::cout << “Move assignment\n”; return *this; }
~MyClass() { std::cout << “Destructor\n”; }
};

MyClass createMyClass() {
return MyClass(); // 返回临时对象
}

int main() {
MyClass obj = createMyClass(); // 这里会发生复制或移动
return 0;

}

在这个例子中,createMyClass函数创建并返回一个MyClass的临时对象。在C++11及更高版本中,编译器通常会优化这种返回临时对象的情况,使用所谓的“返回值优化”(Return Value Optimization, RVO)或“移动语义”来避免不必要的复制。不过,即使没有优化,临时对象也会通过复制或移动构造绑定到obj上。
例子3:使用std::pair和std::make_pair

std::pair是标准库中的一个模板类,用于存储一对值。std::make_pair是一个函数模板,用于创建并返回一个std::pair对象。

cpp

#include
#include // for std::pair, std::make_pair

int main() {
auto p = std::make_pair(1, 2.5); // 创建一个临时std::pair对象并绑定到p
std::cout << "Pair: " << p.first << ", " << p.second << ‘\n’;

// 注意:在C++11及更高版本中,auto关键字可以自动推导类型
// 这里p的类型是std::pair<int, double>

return 0;

}

在这个例子中,std::make_pair(1, 2.5)创建了一个std::pair<int, double>的临时对象,并将其绑定到变量p上。这里同样利用了移动语义或复制构造函数来避免性能损失。

这些例子展示了临时对象在不同上下文中的绑定和使用,特别是在涉及到右值引用和移动语义的现代C++编程中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九江在天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值