2010-04-05 70 views
22

我有一个C++程序使用包含类实例的std :: list。如果我打电话给它经历了创建一个临时变量的过程,然后立即将它复制到该向量,然后删除该临时变量。这并不像我想要的那么高效,而且当您需要深度复制时会很糟糕。如何在不调用复制构造函数的情况下使用类初始化STL向量/列表

我很想有我的类new的构造函数的东西,而不必实现一个拷贝构造函数只是为了第二次分配我的内存并浪费运行时。我宁愿不必立即从vector/list中找到类实例,然后手动分配内存(或者做一些可怕的事情,比如在拷贝构造函数中分配内存)。

有没有办法解决这个问题(我没有使用Visual Studio BTW)?

+1

你使用什么编译器?我相信这种情况通常会在较新的编译器中进行优化。 – 2010-04-05 18:27:48

+0

您是否使用优化版本进行分析? – 2010-04-05 18:33:25

+0

您需要使用发布版本进行测试。它可能在DEBUG模式下执行此操作,但在释放模式下使用RVO(返回值优化),从而消除了副本。 – Nate 2010-04-05 18:42:11

回答

9

C++ 0x移动构造函数是一个部分解决方法:而不是被调用的复制构造函数,移动构造函数会是。移动构造函数就像复制构造函数,除了它允许使源参数失效。

C++ 0x增加另一个功能,它会做你想要的:emplace_back。 (N3092§23.2。3)将参数传递给构造函数,然后用参数(由...和转发)调用构造函数,所以不能调用其他构造函数。

至于C++ 03,你唯一的选择是给你的类添加一个未初始化的状态。在push_back之后立即调用另一个函数执行实际构造。 boost::optional可能会帮助你避免初始化班级的成员,但它又需要他们是可复制构建的。或者,正如弗雷德所说,用最初空白的智能指针来完成同样的事情。

+0

这就是我要找的东西。我有一个支持C++ 0x的编译器,就像std :: lists的便利,没有大量指针的内存/堆分配缺陷。有趣的是,我从来没有听说过emplace_back,这是我刚刚查看C++ 0x的维基百科条目所得到的结果。我在哪里可以找到更多关于它的信息? – Warpspace 2010-04-06 06:03:20

+0

@Warpspace:下载N3092(谷歌搜索“C++ N3092”),查看Stroustrup的C++ 0x FAQ http://www2.research.att.com/~bs/C++0xFAQ.html。我注意到微软对'emplace'有不正确的文档,声称它使用移动语义。 (同样的事情是通过'push_back(move(...))'完成的。)所以,如果你使用MSVC,那么你现在可能还是不走运。 – Potatoswatter 2010-04-06 07:25:03

4

事实上,在这种情况下编译器可能会删除副本。

如果您的编译器不这样做,避免复制的一种方法是让您的列表包含指针而不是实例。您可以使用智能指针为您清理对象。

+2

编译器无法删除副本。该对象在堆栈上构建,并通过const引用传递给'vector <> :: allocator_type :: construct'将其移动到堆中。它是在对'push_back'的调用开始之前构建的,直到'push_back'内的'allocator_type :: allocate'才知道目标位置。时间和空间的力量不合作。 – Potatoswatter 2010-04-05 23:04:41

2

查看Boost的ptr_container库。我用ptr_vector特别是:

boost::ptr_vector<Foo> c; 
c.push_back(new Foo(1,2,3)); 
c[0].doSomething() 

并且当其超出范围,delete将在向量中的每个元素被调用。

+1

...没有'shared_ptr'的开销 - +1 – vladr 2010-04-06 02:50:16

1

使用shared_ptrshared_array来管理你的类想要分配的内存。然后,编译器提供的拷贝构造函数将简单地增加一个引用计数,因为shared_ptr拷贝自己。对于标准容器来说,这是一个重要的使用概念,您的元素的复制便宜。标准库在整个地方进行复制。

5

C++ 0x移动构造函数(可用于VC++ 2010和最近的GNU编译器)正是你正在寻找的。

+0

移动构造函数需要对象的未初始化状态。如果他有一个未初始化的状态,他可以用'push_back(MyClass())'默认构建到那个状态,然后'初始化'他的对象。所以我不认为'移动'在这里解决任何问题。 – Potatoswatter 2010-04-05 23:17:26

+0

我不确定它是否是我的编译器,但启用C++ 0x(在G ++下)后,移动构造函数似乎不被调用,因为我的对象被删除两次(意味着使用了复制构造函数)。 Potatocorn提到的是我目前的实现,我试图避免由于严重代码重复(或需要专用功能)。 – Warpspace 2010-04-06 06:24:13

9

Ahem。科学的利益,我已经掀起了一个小的测试程序来检查编译器是否elides副本或不:

#include <iostream> 
#include <list> 
using namespace std; 

class Test 
{ 
public: 
    Test() { cout<<"Construct\n"; } 
    Test(const Test& other) { cout<<"Copy\n"; } 
    Test& operator=(const Test& other) { cout<<"Assign\n"; return (*this); } 
}; 

Test rvo() { return Test(); } 
int main() 
{ 
    cout<<"Testing rvo:\n"; 
    Test t = rvo(); 
    cout<<"Testing list insert:\n"; 
    list<Test> l; 
    l.push_back(Test()); 
} 

下面是我的MSVC++输出2008:

 
Testing rvo: 
Construct 
Testing list insert: 
Construct 
Copy 

对于调试版本和发布版本来说都是一样的:RVO的工作原理,临时对象传递并没有被优化。
如果我没有弄错,在C++ 0x标准中添加Rvalue references是为了解决这个问题。

+1

+1科学**! – 2010-04-05 22:44:54

0

我建议使用std::vector<std::unique_ptr>,因为它会在需要时自动释放内存,开销比std::shared_ptr更少。唯一需要注意的是这个指针没有引用计数,所以你必须小心,不要将指针本身复制到其他地方,以免数据在其他地方仍然被使用时被删除。

相关问题