题目

编写程序,设计一个记录默认构造、有参构造、拷贝构造与移动构造调用日志的测试类 Widget。向 std::vector<Widget> 中分别使用 push_back 与 emplace_back 插入带有多个构造参数的对象,精确追踪并对比临时对象的生成、移动与直接在容器分配的堆内存上进行“原地就地构造”(In-place construction)的性能差异。

输入格式:无需外部输入。

输出格式:输出 push_back 与 emplace_back 在对象构造全周期的控制台执行轨迹。

数据范围与约定:变长参数模板、完美转发与原地构造。

思路与算法

1. push_back 的调用链路
当传入构造参数时(如 vec.push_back(Widget(1, "A"))),必须先在调用方栈上显式或隐式调用有参构造生成一个临时右值对象,随后将其通过移动构造(或拷贝构造)迁移到容器分配的堆槽位中,最后再析构栈上的临时对象。

2. emplace_back 的原地机制
emplace_back(args...) 利用可变参数模板与 std::forward 完美转发,直接把实参包传递到底层通过定位 new(Placement new ::new(ptr) T(args...))在预留堆槽位中直接初始化,从根本上省去临时对象的构造与析构。

3. 已经存在左值时的考量
若对象已经在外部构造完成为左值变量 w,此时无论是 push_back(w) 还是 emplace_back(w) 均只能触发拷贝构造,两者没有实质差异。

完整程序

#include <iostream>
#include <vector>
#include <string>

class Widget {
public:
    int id;
    std::string tag;

    Widget(int i, std::string t) : id(i), tag(std::move(t)) {
        std::cout << "  [有参构造] Widget(" << id << ", " << tag << ")\n";
    }

    Widget(const Widget& other) : id(other.id), tag(other.tag) {
        std::cout << "  [拷贝构造] Widget 从 (" << other.id << ")\n";
    }

    Widget(Widget&& other) noexcept : id(other.id), tag(std::move(other.tag)) {
        std::cout << "  [移动构造] Widget 从 (" << other.id << ")\n";
    }

    ~Widget() {
        std::cout << "  [析构函数] Widget(" << id << ")\n";
    }
};

int main() {
    std::vector<Widget> list1;
    list1.reserve(5); // 预分配容量,排除扩容迁移干扰

    std::cout << "=== 1. push_back 传临时对象 ===\n";
    list1.push_back(Widget(101, "PushWidget"));

    std::cout << "\n=== 2. emplace_back 原地就地构造 ===\n";
    list1.emplace_back(202, "EmplaceWidget");

    std::cout << "\n=== 3. 容器作用域结束整体释放 ===\n";
    return 0;
}

运行示例

输出:

=== 1. push_back 传临时对象 ===
  [有参构造] Widget(101, PushWidget)
  [移动构造] Widget 从 (101)
  [析构函数] Widget(101)

=== 2. emplace_back 原地就地构造 ===
  [有参构造] Widget(202, EmplaceWidget)

=== 3. 容器作用域结束整体释放 ===
  [析构函数] Widget(101)
  [析构函数] Widget(202)