【问题标题】:How to initialise a STL vector/list with a class without invoking the copy constructor如何在不调用复制构造函数的情况下使用类初始化 STL 向量/列表
【发布时间】:2011-02-04 12:42:52
【问题描述】:

我有一个 C++ 程序,它使用包含类实例的 std::list。如果我打电话给例如myList.push_back(MyClass(variable)); 它会先创建一个临时变量,然后立即将其复制到向量中,然后将临时变量删除。这几乎没有我想要的效率,而且当你需要一个深拷贝时很糟糕。

我很想拥有我的类new 的构造函数,而不必为了第二次分配我的内存并浪费运行时间而实现复制构造函数。我也不想立即从向量/列表中找到类实例,然后手动分配内存(或者做一些可怕的事情,比如在复制构造函数本身中分配内存)。

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

【问题讨论】:

  • 你用的是什么编译器?我相信这种情况通常在较新的编译器中进行了优化。
  • 您是否使用优化的构建执行此分析?
  • 您需要使用发布版本进行测试。它可能在 DEBUG 模式下执行此操作,但在发布模式下使用 RVO(返回值优化),从而消除了副本。
  • 这与 RVO 不同;我刚刚在 MSVC2008 上进行了测试,虽然发布版本确实执行了 RVO,但在这种情况下它仍然会复制,即使启用了所有优化。当我需要这样做时,我通常只使用指针容器。
  • 有点题外话:Visual C++ 即使在调试版本中也执行 RVO,但这并不是真正的 RVO 场景。

标签: c++ list stl vector


【解决方案1】:

C++0x 移动构造函数是一种部分解决方法:不是调用复制构造函数,而是调用移动构造函数。移动构造函数类似于复制构造函数,只是它允许使源参数无效。

C++0x 添加了 另一个 功能,可以完全满足您的需求:emplace_back。 (N3092 §23.2.3)您将参数传递给构造函数,然后它使用这些参数调用构造函数(通过... 和转发),因此无法调用其他构造函数。 p>

对于 C++03,您唯一的选择是为您的类添加一个未初始化的状态。在push_back 之后立即调用的另一个函数中执行实际构造。 boost::optional 可能会帮助您避免初始化类的成员,但它反过来要求 它们 是可复制构造的。或者,正如 Fred 所说,使用最初为空的智能指针完成同样的事情。

【讨论】:

  • 这就是我想要的。我有一个启用 C++0x 的编译器,并且喜欢 std::lists 的便利性,没有大量指针的内存/堆分配缺点。有趣的是,我从来没有听说过 emplace_back,这就是我在查看 C++0x 的维基百科条目时得到的。我在哪里可以找到有关它的更多信息?
  • @Warpspace:下载 N3092(谷歌搜索“C++ N3092”)并查看 Stroustrup 的 C++0x 常见问题解答www2.research.att.com/~bs/C++0xFAQ.html。我注意到微软对emplace 的文档不正确,声称它使用了移动语义。 (push_back(move(…)) 也完成了同样的事情。)因此,如果您使用的是 MSVC,那么您目前可能仍然不走运。
【解决方案2】:

咳咳。为了科学,我编写了一个小测试程序来检查编译器是否忽略了副本:

#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 上的输出:

测试 rvo: 构造 测试列表插入: 构造 复制

调试和发布版本都是一样的:RVO 有效,临时对象传递未优化。
如果我没记错的话,C++0x 标准中添加的Rvalue references 就是为了解决这个问题。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 移动构造函数需要对象的未初始化状态。如果他有一个未初始化的状态,他可以默认构造到 push_back(MyClass()) 然后 initialize 他的对象到位。所以我不认为move 在这里解决任何问题。
    • 我不确定它是否是我的编译器,但是启用 C++0x(在 G++ 下)后,似乎没有调用移动构造函数,因为我的对象被删除了两次(暗示使用了复制构造函数)。 Potatocorn 提到的是我当前的实现,由于严重的代码重复(或需要专用函数),我试图避免这种情况。
    【解决方案4】:

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

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

    【讨论】:

    • 编译器无法省略副本。该对象在堆栈上构造并通过 const 引用传递给vector&lt;&gt;::allocator_type::construct 以将其移动到堆中。它是在对push_back 的调用开始之前构建的,并且直到allocator_type::allocate 内的push_back 才知道目标位置。时间和空间的力量不合作。
    【解决方案5】:

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

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

    当它超出范围时,将在向量的每个元素上调用delete

    【讨论】:

    • ...没有shared_ptr 的开销 -- +1
    【解决方案6】:

    使用shared_ptrshared_array 来管理你的类想要分配的内存。然后编译器提供的复制构造函数将在shared_ptr 复制自身时简单地增加引用计数。对于标准容器来说,一个重要的使用概念是你的元素复制起来很便宜。标准库在各处制作副本。

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-12-06
        • 1970-01-01
        • 2018-07-05
        • 1970-01-01
        • 2012-12-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多