c ++:过度复制大对象

时间:2011-05-10 18:25:46

标签: c++ copy-constructor assignment-operator

虽然关于SO的拷贝构造函数/赋值运算符已经存在很多问题,但我找不到适合我问题的答案。

我有一个像

这样的课程
class Foo
{
   // ...
private:
   std::vector<int> vec1;
   std::vector<int> vec2;
   boost::bimap<unsigned int, unsigned int> bimap;
   // And a couple more
};

现在似乎有一些相当多的复制(基于个人资料数据)。所以我的问题是如何最好地解决这个问题?

我应该实现自定义复制构造函数/赋值运算符并使用swap吗?或者我应该定义自己的交换方法并使用它(在适当的情况下)而不是赋值?

由于我不是c ++专家,因此非常感谢能够正确处理这种情况的示例。

更新:看来我不太清楚..让我试着解释一下。该程序基本上是一个动态广度优先的搜索程序,并且对于每个步骤,我需要存储关于步骤的元数据(这是Foo类)。现在问题是存在(通常)指数级,所以你可以想象需要存储大量这些对象..我总是通过(const)引用,据我所知。每次我从图中的一个节点计算一个后继需要创建和存储ONE Foo对象(但是,在处理此后继者时,某些数据成员将进一步添加到此foo ..)

我的个人资料数据显示大致类似的内容(我这台机器上没有实际的数字):

SearchStrategy::Search    13s
FooStore::Save            10s

所以你可以看到我花在保存这个元数据上的时间和搜索图表的时间差不多。哦,FooStore将Foo保存在google::sparse_hash_map<long long, Foo, boost::hash<long long> >中。

编译器是g ++ 4.4或g ++ 4.5(我不在我的开发机器上,所以我现在无法检查)..

更新2 我将构建后的一些成员分配给Foo实例,如

void SetVec1(const std::vector<int>& vec1) { this->vec1 = vec1; };

我想明天,我应该改变这个以使用swap方法,这肯定会改善这一点..

如果我不完全清楚我想要达到的语义,我很抱歉,但原因是我不太确定。

此致

的Morten

6 个答案:

答案 0 :(得分:3)

一切都取决于复制此对象在您的情况下的含义:

  1. 意味着复制它的整体价值
  2. 表示复制的对象将引用相同的内容
  3. 如果它是1,那么这个类似乎是正确的。你不是很清楚你说的那些操作会制作大量的副本,所以我假设你试图复制整个对象。

    如果它是2,那么你需要使用像shared_ptr这样的东西来共享对象之间的容器。仅使用shared_ptr而不是真实对象作为成员将隐含地允许缓冲区被两个对象(复制和复制)引用。 这是更简单的方法(使用boost :: shared_ptr或std :: shared_ptr,如果你有一个C ++ 0x启用编译器提供它)。

    有更难的方法,但它们肯定会在以后成为一个问题。

答案 1 :(得分:2)

  1. 当然,每个人都这么说,不要过早优化。除非你证明a)你的程序运行得太慢,否则不要费心去做,b)如果你没有复制那么多数据,它会更快。

  2. 如果您的程序设计要求您同时保存多个数据副本,则无法执行任何操作。你只需要咬紧牙关并复制数据。不,实现自定义复制构造函数和自定义赋值运算符不会使其更快。

  3. 如果您的程序不需要同时复制此数据,那么您可以通过一些技巧来减少执行的副本数量。

  4. 检测您的复制方法如果是我,我要做的第一件事,即使在尝试改进任何事情之前,也要计算我的复制方法的次数。 调用

    class Foo {
    private:
      static int numberOfConstructors;
      static int numberofCopyConstructors;
      static int numberofAssignments;
      Foo() { ++numberOfConstructors; ...; }
      Foo(const Foo& f) : vec1(f.vec1), vec2(f.vec2), bimap(f.bimap) {
        ++numberOfCopyConstructors;
        ...;
      }
      Foo& operator=(const Foo& f) {
        ++numberOfAssignments;
        ...;
      }
    };
    

    使用和不使用您的改进来运行您的程序。打印出这些静态成员的值,看看你的更改是否有效。

    通过使用引用避免函数调用中的赋值如果将Foo类型的对象传递给函数,请考虑是否可以通过引用来完成。如果你不改变传递的副本,那么通过const引用传递它是一件很容易的事。

    // WAS:
    extern SomeFuncton(Foo f);
    // EASY change -- if this compiles, you know that it is correct
    extern SomeFunction(const Foo& f);
    // HARD change -- you have to examine your code to see if this is safe
    extern SomeFunction(Foo& f);
    

    使用Foo :: swap 避免复制如果您使用复制方法(显式或隐式),请考虑assign-from项是否可以放弃其数据,而不是复制它

    // Was:
    vectorOfFoo.push_back(myFoo);
    // maybe faster:
    vectorOfFoo.push_back(Foo());
    vectorOfFoo.back().swap(myFoo);
    
    // Was:
    newFoo = oldFoo;
    // maybe faster
    newfoo.swap(oldFoo);
    

    当然,这仅在myFoooldFoo不再需要访问其数据时才有效。而且,您必须实施Foo::swap

    void Foo::swap(Foo& old) {
        std::swap(this->vec1, old.vec1);
        std::swap(this->vec2, old.vec2);
        ...
    }
    

    无论您做什么,在更改之后之前测量您的程序。衡量复制方法的调用次数,以及程序的总时间改进。

答案 2 :(得分:1)

你的课程似乎并不那么糟糕,但你没有表明你如何使用它。

如果有大量复制,则需要通过引用(或者如果可能的const引用)传递这些类的对象。 如果必须复制该类,那么你就无法做任何事情。

答案 3 :(得分:1)

如果确实存在问题,您可以考虑实施pimpl idiom。但我怀疑这是一个问题,虽然我必须看到你对这个课程的使用是肯定的。

答案 4 :(得分:1)

不太可能复制巨大的载体可能很便宜。最有希望的方法是复制稀有。虽然在C ++中很容易(可能太容易)无意地调用副本,但有一些方法可以避免不必要的复制:

  • 传递const和非const引用
  • 移动-构造
  • 拥有所有权转移的智能指针

这些技术可能只留下算法所需的副本。

有时甚至可以避免一些复制。例如,如果您需要两个对象,其中第二个对象是第一个对象的反转副本,则可能会创建一个包装对象,其行为类似于反转,但不是存储整个副本只有一个引用。

答案 5 :(得分:0)

减少复制的显而易见的方法是使用类似shared_ptr的东西。然而,对于多线程,这种治疗方法可能比疾病更糟糕 - 递增和递减参考计数需要以原子方式进行,这可能非常昂贵。但是,如果您通常最终修改副本并且需要每个副本执行独特操作(即,修改副本不会影响原始副本),那么最终可能会导致性能下降,为引用计数支付原子增量/减量,无论如何还是做了很多副本。

有几种明显的方法可以避免这种情况。一种是移动独特的对象而不是复制 - 如果你可以使它工作,这是很好的。另一种方法是在大多数情况下使用非原子引用计数,并仅在线程之间移动数据时执行深层复制。

没有一个答案是“普遍而且非常干净”。