TypechoJoeTheme

至尊技术网

统计
登录
用户名
密码

vector的emplace_back和push_back有什么区别移动构造与完美转发原理

2025-07-24
/
0 评论
/
3 阅读
/
正在检测是否收录...
07/24

引言:为什么需要emplace_back?

在C++11之前,我们向容器添加元素通常使用pushback方法。然而随着移动语义和完美转发的引入,C++11为我们提供了更高效的emplaceback方法。理解这两种方法的区别及其背后的原理,对于编写高效的现代C++代码至关重要。

1. pushback与emplaceback的基本区别

push_back的工作方式相对直接:

cpp std::vector<std::string> vec; vec.push_back("Hello"); // 创建临时string对象,然后拷贝或移动到vector中

emplace_back则更加高效:

cpp vec.emplace_back("Hello"); // 直接在vector内存中构造string对象

关键区别在于:
- pushback:接受一个已构造的对象(或能隐式转换为容器元素类型的对象) - emplaceback:接受构造参数,在容器内部直接构造对象

2. 性能差异分析

考虑以下复杂对象的例子:

cpp
class Person {
public:
Person(const std::string& name, int age) : name(name), age(age) {
std::cout << "Constructing Person\n";
}

Person(const Person& other) : name_(other.name_), age_(other.age_) {
    std::cout << "Copying Person\n";
}

Person(Person&& other) noexcept : name_(std::move(other.name_)), age_(other.age_) {
    std::cout << "Moving Person\n";
}

private:
std::string name_;
int age_;
};

使用push_back:

cpp std::vector<Person> people; people.push_back(Person("Alice", 30)); // 输出: // Constructing Person (临时对象) // Moving Person (移动到vector中)

使用emplace_back:

cpp people.emplace_back("Bob", 25); // 输出: // Constructing Person (直接在vector中构造)

emplace_back避免了临时对象的创建和移动操作,性能更优。

3. 移动构造原理

移动语义是C++11引入的重要特性,通过右值引用(&&)实现:

cpp Person(Person&& other) noexcept : name_(std::move(other.name_)), age_(other.age_) { // 移动后应使other处于有效但不确定的状态 }

关键点:
- 移动构造函数接受右值引用参数
- 使用std::move将成员变量从源对象"窃取"过来
- 移动后源对象应处于有效但不确定的状态
- 标记为noexcept以便容器在重新分配内存时使用移动而非拷贝

4. 完美转发原理

emplace_back的高效性源于完美转发(perfect forwarding)技术:

cpp template <typename... Args> void emplace_back(Args&&... args) { // 在vector内存中直接构造元素 allocator_traits::construct(allocator, end_ptr, std::forward<Args>(args)...); ++end_ptr; }

完美转发依赖两个关键机制:
1. 通用引用(universal reference):Args&&能匹配任何类型的参数(左值或右值)
2. std::forward:根据参数的原始类型(左值/右值)进行有条件转换

std::forward的实现原理:

cpp template <typename T> T&& forward(typename std::remove_reference<T>::type& arg) noexcept { return static_cast<T&&>(arg); }

当传递左值时,T推导为左值引用;传递右值时,T推导为非引用类型。static_cast保持原始值类别不变。

5. 实际应用建议

  1. 优先使用emplace_back



    • 当构造参数已知时
    • 对象构造成本高时
  2. 使用push_back的情况



    • 已有构造好的对象需要添加
    • 需要明确表达意图(代码可读性考虑)
  3. 移动语义的最佳实践



    • 为资源管理类实现移动操作
    • 移动构造函数应标记为noexcept
    • 移动后使源对象处于有效状态(通常为空或默认状态)

6. 陷阱与注意事项

  1. 显式构造函数的问题:cpp
    struct Explicit {
    explicit Explicit(int) {}
    };

    std::vector vec;
    vec.pushback(10); // 错误:不能隐式转换 vec.emplaceback(10); // 正确:直接构造

  2. 参数求值顺序
    emplace_back的参数求值顺序未指定,可能导致意外行为:
    cpp vec.emplace_back(foo(), bar()); // foo和bar的调用顺序不确定

  3. 内存重新分配的影响
    即使使用emplace_back,vector扩容时仍可能引发对象移动或拷贝,因此预先reserve()能进一步提升性能。

7. 性能测试对比

通过简单的性能测试可以直观看到差异:

cpp

include

include

include

include

const int COUNT = 1000000;

void testpushback() {
std::vector vec;
vec.reserve(COUNT);

auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < COUNT; ++i) {
    vec.push_back("test string");
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "push_back: " 
          << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() 
          << " ms\n";

}

void testemplaceback() {
std::vector vec;
vec.reserve(COUNT);

auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < COUNT; ++i) {
    vec.emplace_back("test string");
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "emplace_back: " 
          << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() 
          << " ms\n";

}

int main() {
testpushback();
testemplaceback();
return 0;
}

典型输出结果:
push_back: 120 ms emplace_back: 80 ms

结论

emplaceback和pushback的选择反映了现代C++对效率的追求。理解其背后的移动语义和完美转发机制,不仅能帮助我们做出正确的API选择,还能指导我们设计更高效的类。记住:

  1. emplace_back通过完美转发直接在容器中构造对象,避免了临时对象的创建
  2. 移动语义使得资源转移而非拷贝成为可能
  3. 完美转发保持了参数的原始值类别(左值/右值)

在实际开发中,应根据具体情况选择最合适的方法,并在性能敏感的场景中进行基准测试以验证优化效果。

当构造参数已知时对象构造成本高时
朗读
赞(0)
版权属于:

至尊技术网

本文链接:

https://www.zzwws.cn/archives/33699/(转载时请注明本文出处及文章链接)

评论 (0)