C++ 拷贝省略与虚拟克隆

C++ 拷贝省略与虚拟克隆,c++,copy-elision,C++,Copy Elision,在以下情况下,如何避免不必要的复制?类A包含指向大对象的基类型指针 class A{ BigBaseClass *ptr; A(const BigBaseClass& ob); ~A(){delete ptr;} }; 有时我需要复制对象ob。因此,我实施虚拟克隆: class BigBaseClass{ virtual BigBaseClass* clone() {return new BigBaseClass(*this);} }; class BigDerive

在以下情况下,如何避免不必要的复制?类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()};

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

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

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是一个基类,而将BigDeriveClass移动到BigBaseClass只会移动BigBaseClass的部分。但是使用智能指针也是一个好主意,除非您确定代码的其余部分没有异常

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

编译器不会通过克隆省略副本的构造:只有在非常特定的情况下才允许省略副本。在允许编译器进行复制省略的所有情况下,所涉及对象的生命周期完全由编译器控制。这四种情况详见12.8[类别副本]第8段:

按值返回本地名称。 投掷一个本地对象。 正在复制未绑定到引用的临时对象。 当按值捕捉时。 即使在这些情况下,复制省略也适用的细节有些不重要。在任何情况下,返回新的T*this;不适合这些情况

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

#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()};
}
移动构造是否有助于复制大对象取决于对象的内部实现方式。如果它们的对象实际上只包含两个指向实际大数据的指针,那么move构造应该避免任何相关成本,因为与设置实际数据相比,转移指针的成本可以忽略不计。如果数据实际上保存在对象中,则传输不会真正起作用,尽管出于各种原因,这样做通常是个坏主意。

编译器不会通过克隆省略副本的构造:只有在非常特定的情况下才允许复制省略。在允许编译器进行复制省略的所有情况下,所涉及对象的生命周期完全由编译器控制。这四种情况详见12.8[类别副本]第8段:

按值返回本地名称。 投掷一个本地对象。 正在复制未绑定到引用的临时对象。 当按值捕捉时。 即使在这些情况下,复制省略也适用的细节有些不重要。在任何情况下,返回新的T*this;不适合这些情况

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

#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()};
}

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

这甚至可以编译吗?您的克隆应该返回动态分配的对象,并且您应该使用动态分配的对象进行初始化。另外,查看std::unique_ptr以避免使用裸指针。哦,对不起,我忘记了克隆函数中的新功能。修正了。这不是移动语义发明的目的吗?如果你用一个接受BigBaseClass*的构造函数替换构造函数,你可以把它分配给ptr,强迫你在堆上分配它。这就是直接在堆上创建对象的方式。一个aBigDerivedClass甚至可以编译吗?你的克隆人应该会回来

动态分配的对象,您应该使用动态分配的对象进行初始化。另外,查看std::unique_ptr以避免使用裸指针。哦,对不起,我忘记了克隆函数中的新功能。修正了。这不是移动语义发明的目的吗?如果你用一个接受BigBaseClass*的构造函数替换构造函数,你可以把它分配给ptr,强迫你在堆上分配它。这就是直接在堆上创建对象的方式。aBigDerivedClass您是说使用两个不同的构造函数——一个使用const引用复制对象,另一个使用指针移动对象?或者你的意思是我可以用aBigDerivedClass_对象进行复制。克隆?后者。获取在堆栈上创建的对象的地址并将其存储在A中可能不是很安全,除非您确定堆栈分配的对象将比A更长寿。最好让它清楚地显示正在发生的事情。好吧,当BigClass_对象由函数的值返回时,这似乎没有什么帮助,但也许这只是意味着我应该避免按值返回大对象。按值返回的对象不应该放在A中,因为它位于堆栈上,当它超出范围时会自动销毁。如果您想同时处理BigBaseClass和BigDerivedClass的实例,那么唯一的方法就是在堆上处理,这意味着需要将对象更新为存在。但是您可以执行由\u value.clone返回的ABigDerivedClass\u对象;您的意思是使用两个不同的构造函数-一个使用const引用复制对象,另一个使用指针移动对象?或者你的意思是我可以用aBigDerivedClass_对象进行复制。克隆?后者。获取在堆栈上创建的对象的地址并将其存储在A中可能不是很安全,除非您确定堆栈分配的对象将比A更长寿。最好让它清楚地显示正在发生的事情。好吧,当BigClass_对象由函数的值返回时,这似乎没有什么帮助,但也许这只是意味着我应该避免按值返回大对象。按值返回的对象不应该放在A中,因为它位于堆栈上,当它超出范围时会自动销毁。如果您想同时处理BigBaseClass和BigDerivedClass的实例,那么唯一的方法就是在堆上处理,这意味着需要将对象更新为存在。但是您可以执行由\u value.clone返回的ABigDerivedClass\u对象;尽管这样做通常是一个坏主意,但出于各种原因,你能列举一些或提供一些与此主题相关的材料的链接吗?@MikeMB:这里有一些:堆栈空间相对有限,导致共定位对象有问题;移动大型物体是不可取的;占用空间大的类往往很难测试;从数组中获取其大小的大型对象往往会施加任意限制。在某些情况下,直接大小较大的对象可能是可以接受的,但在大多数情况下,我看到最好使用实际表示的小句柄。谢谢您的列表。我完全同意关于数组的观点,但除此之外,我在这里有点矛盾:我倾向于将数据应该放在哪里的决定转移到使用方面:如果数据应该放在堆上,就用智能指针包装它。这通常会使课程更简单,效率略高,但当然会使营地复杂化。不过,我并不真正了解可测试性这一部分——它是如何与类的内存占用相联系的?@MimeMB:re-testability:具有较大占用空间的类通常由一组元素或许多不同的组件组成。在后一种情况下,类可能相当复杂,不太可能是特定的可测试类。如果类的占用空间很小,那么它就没有那么复杂,并且由可以单独测试的部分组成。当然,也有例外,如果简单的类仍然很大,事情就可以组合起来。尽管出于各种原因,这样做通常不是个好主意,但你能列举一些或提供一些与此主题相关的材料的链接吗?@MikeMB:这里有一些:堆栈空间相对有限,导致共定位对象有问题;移动大型物体是不可取的;占用空间大的类往往很难测试;从数组中获取其大小的大型对象往往会施加任意限制。在某些情况下,直接大小较大的对象可能是可以接受的,但在大多数情况下,我看到最好使用实际表示的小句柄。谢谢您的列表。我完全同意关于数组的观点,但除此之外,我在这里有点矛盾:我倾向于将数据应该放在哪里的决定转移到使用方面:如果数据应该放在堆上,就用智能指针包装它。这通常会使类变得更简单、更简单
效率更高,但当然会使营地复杂化。不过,我并不真正了解可测试性这一部分——它是如何与类的内存占用相联系的?@MimeMB:re-testability:具有较大占用空间的类通常由一组元素或许多不同的组件组成。在后一种情况下,类可能相当复杂,不太可能是特定的可测试类。如果类的占用空间很小,那么它就没有那么复杂,并且由可以单独测试的部分组成。当然,也有例外,如果简单的类仍然很大,事情就可以组合起来。不过,这种情况比较少见。