1

在以下场景中如何避免不必要的复制?A 类包含指向大对象的基类型指针。

class A{
  BigBaseClass *ptr;
  A(const BigBaseClass& ob);
  ~A(){delete ptr;}
};

有时我需要复制对象ob 。所以我实现了虚拟克隆:

class BigBaseClass{
   virtual BigBaseClass* clone() {return new BigBaseClass(*this);}
};
class BigDerivedClass : BigBaseClass{
  virtual BigDerivedClass* clone() {return new BigDerivedClass(*this);}
};
A::A(const BigBaseClass& ob):ptr(ob.clone(){}

但有时我会创建临时的 BigDerivedClass 对象并使用它来构造 A 类:

A a{BigDerivedClass()};

或者

BigDerivedClass f(){
     BigDerivedClass b;
     /*constructing object*/
     return b;
   }
   A a{f()};

这里不需要复制创建的对象然后删除它。可以直接在堆中创建此对象并将其地址存储在a.ptr中。

但在我看来,编译器不太可能聪明到在这里实现复制省略(或者是吗?)。那么你会建议什么来避免这种不必要的复制呢?

4

2 回答 2

4

编译器不会通过 : 省略复制的构造clone():复制省略仅在非常特定的情况下允许。在允许编译器进行复制省略的所有情况下,所涉及对象的生命周期完全由编译器控制。这四种情况是(详见 12.8 [class.copy] 第 8 段):

  1. 按值返回本地名称。
  2. 抛出一个本地对象。
  3. 复制未绑定到引用的临时对象。
  4. 按价值捕捉时。

即使在这些情况下也可以应用复制省略的细节有些重要。无论如何,return new T(*this);不​​适合任何这些情况。

典型的大对象不会将其数据作为对象的一部分。相反,它们通常保存一些可以移动的数据结构。如果您想在使用时保持简单性A{f()}而不想要复制 的结果f(),您可以使用移动构造函数调用一个virtual函数来传输内容而不是复制它:

#include <utility>

class BigBaseClass {
public:
    virtual ~BigBaseClass() {}
    virtual BigBaseClass* clone() const = 0;
    virtual BigBaseClass* transfer() && = 0;
};
class A{
    BigBaseClass *ptr;
public:
    A(BigBaseClass&& obj): ptr(std::move(obj).transfer()) {}
    A(BigBaseClass const& obj): ptr(obj.clone()) {}
    ~A(){delete ptr;}
};
class BigDerivedClass
    : public BigBaseClass {
    BigDerivedClass(BigDerivedClass const&); // copy the content
    BigDerivedClass(BigDerivedClass&&);      // transfer the content
    BigDerivedClass* clone() const { return new BigDerivedClass(*this); }
    BigDerivedClass* transfer() && { return new BigDerivedClass(std::move(*this)); }
};

BigDerivedClass f() {
    return BigDerivedClass();
}

int main()
{
    A a{f()};
}

移动构造是否有助于复制大对象确实取决于对象的内部实现方式。如果它们的对象本质上只包含几个指向实际大数据的指针,则移动构造应该避免任何相关成本,因为与设置实际数据相比,传输指针可以忽略不计。如果数据实际上保存在对象中,则传输不会真正有帮助(尽管出于各种原因,这样做通常不是一个好主意)。

于 2016-07-03T23:08:20.140 回答
0
class BigBaseClass
{
public:
    virtual ~BigBaseClass() {}

    virtual BigBaseClass* clone() const { return new BigBaseClass(*this); }
};

class BigDerivedClass : public BigBaseClass
{
public:
    BigDerivedClass* clone() const override { return new BigDerivedClass(*this); }
};

class A
{
    BigBaseClass *ptr;

public:
    explicit A(BigBaseClass* ob);
    ~A() { delete ptr; }
};

A::A(BigBaseClass* ob) : ptr(ob)
{
}

int main()
{
    A a(new BigDerivedClass);
}

您可能认为移动语义是一个好主意,但在这种情况下这并不适用,因为 BigBaseClass 是一个基类,将 BigDerivedClass 移动到 BigBaseClass 只会移动 BigBaseClass 部分。但是使用智能指针也是个好主意,除非您确定其余代码没有异常。

于 2016-07-03T22:47:36.853 回答